Introduction.
This notebook reports on the inference of microbial association networks from meta-taxonomic data extracted from DairyFMBN using package NetCoMi, after some pre-processing carried out mostly with package phyloseq.
The objective of this analysis is:
infer microbial association networks (at the genus level) for selected cheese studies stored in DairyFMBN usign different inference methods
identify associations which are conserved across methods
compare different inference methods
identify and compare hubs in different networks
The data used in this script include studies from FMBN versions 3.2, with different platforms and target, but analyzed using the same pipeline based on DADA2. The script is designed to work with phyloseq objects extracted from FoodMicrobionet or DairyFMBN using the ShinyFMBN app DairyFMBN can be downloaded from Mendeley data: new versions are due soon.
The script will also work correctly with phyloseq objects generated using the DADA2 BioConductor pipeline but the use of SILVA as a taxonomic database is advisable. With some adaptations the script may work with phyloseq objects generated using other pipelines.
Analysis workflow.
start from n (n>1) phyloseq objects. In this analysis I will use phyloseq objects extracted from DairyFMBN (which, in turn, includes all studies on dairy products stored in FoodMicrobionet). Although pooling at the genus level is likely to muddle some interactions, it it the only reasonable choice when comparing studies with vastly different targets, sequencing platforms, bioinformatic pilelines, etc.
perform data-wrangling with phyloseq functions (mostly). There are a number of basic operations which should be performed:
remove samples with a low number of sequences (minseq) using the prune_samples() functions (and perhaps skip the analysis if there is less than min_samples). Note that sample and taxonomic filtering can be also performed within the NetCoMi::netConstruct() function
remove Eukaryota and Chloroplasts using subset_taxa()
remove taxa with ambiguous identification or poor identification (say at the domain or phylum level)
perform prevalence and abundance filtering (prevalence above min_prev AND abundance above min_rel_ab), possibly using filter_taxa()
return some sort of summary for what has been done and the effect on the original table (in terms of loss of taxa, sequences, samples), including some measure of diversity and evenness
perform microbial association network inference with netConstruct() with some error trapping and store results in a list. This example uses the following inference methods:
SparCC (sparcc() in SpiecEasi package): needs high number of taxa and sparse matrix
CCREPE (ccrepe package, it is the main approach used in CoNet)
SpiecEasi (SpiecEasi package)
SPRING (SPRING package)
get nodes, edges and network stats: use NetCoMi::netAnalyze() to get an object with both node and network stats; print a summary for network stats; build a tidygraph object for further stats; build tidy data frames with relevant stats;
The script is esigned to save data and amny of the plots in a subfolder (here “netcompare_output”).
The analysis.
Setting the options.
A number of options pertaining to the analysis or rlated to save operations can be set in the chunk below and will be saved and printed.
$general_options
$general_options$ncores
[1] 2
$general_options$data_folder
[1] "input_data"
$general_options$source_folder
[1] "source"
$general_options$output_folder
[1] "netcompare_output"
$general_options$process_batch
[1] TRUE
$general_options$use_lookup
[1] TRUE
$general_options$out_filenames
[1] "MAN_FMBN_genus_3"
$general_options$gres
[1] 300
$general_options$gtype
[1] "tiff"
$methods_options
$methods_options$methods
[1] "spieceasi" "spring" "sparcc" "ccrepe"
$methods_options$parameters
$methods_options$parameters$spieaceasi
$methods_options$parameters$spieaceasi$sparMethod
[1] "t-test"
$methods_options$parameters$spieaceasi$alpha
[1] 0.001
$methods_options$parameters$spieaceasi$measureParList
$methods_options$parameters$spieaceasi$measureParList$method
[1] "mb"
$methods_options$parameters$spieaceasi$measureParList$lambda.min.ratio
[1] 0.01
$methods_options$parameters$spieaceasi$measureParList$nlambda
[1] 20
$methods_options$parameters$spieaceasi$measureParList$pulsar.params
$methods_options$parameters$spieaceasi$measureParList$pulsar.params$rep.num
[1] 50
$methods_options$parameters$spieaceasi$normmethodPar
[1] "none"
$methods_options$parameters$spieaceasi$zeromethodPar
[1] "none"
$methods_options$parameters$spieaceasi$dissFuncPar
[1] "signed"
$methods_options$parameters$spieaceasi$verbosePar
[1] 1
$methods_options$parameters$spring
$methods_options$parameters$spring$sparMethod
[1] "t-test"
$methods_options$parameters$spring$alpha
[1] 0.001
$methods_options$parameters$spring$measureParList
$methods_options$parameters$spring$measureParList$nlambda
[1] 50
$methods_options$parameters$spring$measureParList$rep.num
[1] 50
$methods_options$parameters$spring$normmethodPar
[1] "none"
$methods_options$parameters$spring$zeromethodPar
[1] "none"
$methods_options$parameters$spring$dissFuncPar
[1] "signed"
$methods_options$parameters$spring$verbosePar
[1] 1
$methods_options$parameters$sparcc
$methods_options$parameters$sparcc$sparMethod
[1] "t-test"
$methods_options$parameters$sparcc$alpha
[1] 0.001
$methods_options$parameters$sparcc$measureParList
$methods_options$parameters$sparcc$measureParList$iter
[1] 100
$methods_options$parameters$sparcc$measureParList$inner_it
[1] 20
$methods_options$parameters$sparcc$measureParList$th
[1] 0.05
$methods_options$parameters$sparcc$normmethodPar
[1] "none"
$methods_options$parameters$sparcc$zeromethodPar
[1] "none"
$methods_options$parameters$sparcc$dissFuncPar
[1] "signed"
$methods_options$parameters$sparcc$verbosePar
[1] 1
$methods_options$parameters$ccrepe
$methods_options$parameters$ccrepe$sparMethod
[1] "t-test"
$methods_options$parameters$ccrepe$alpha
[1] 0.001
$methods_options$parameters$ccrepe$measureParList
NULL
$methods_options$parameters$ccrepe$normmethodPar
[1] "fractions"
$methods_options$parameters$ccrepe$zeromethodPar
[1] "none"
$methods_options$parameters$ccrepe$dissFuncPar
[1] "signed"
$methods_options$parameters$ccrepe$verbosePar
[1] 1
$filtering_options
$filtering_options$min_samples
[1] 20
$filtering_options$min_seqs
[1] 1000
$filtering_options$glom_taxa
[1] "genus"
$filtering_options$rmunchar_dp
[1] TRUE
$filtering_options$rmchlmit
[1] TRUE
$filtering_options$rmeuk
[1] TRUE
$filtering_options$rmunchar_cof
[1] TRUE
$filtering_options$filter_OTUs
[1] TRUE
$filtering_options$prevfilter
[1] TRUE
$filtering_options$prevthreshold
[1] 0.05
$filtering_options$abthreshols
[1] 0.005
$filtering_options$passboth
[1] TRUE
$filtering_options$saveprevabplot
[1] TRUE
$filtering_options$printprevabplot
[1] FALSE
$filtering_options$saveprevabtable
[1] TRUE
$filtering_options$save_prev_ab_list
[1] TRUE
$netstat_options
$netstat_options$doVenn
[1] TRUE
$netstat_options$calcEbetw
[1] TRUE
$netstat_options$mergeNstats
[1] TRUE
Loading the objects to process.
The input data are stored in a folder (input_data) and processed one by one (depending on user input) or in batch (see process_batch option). The folder should also contain a .tsv with study metadata, with a label variable with the number of the study and an indicator on the nature of the file (FMBN or, if the accn number is in the name, ASV). Here I am loading 5 studies from DairyFMBN (ST106, ST110, ST115, ST131, ST136). The studies differ in the targets (ST106 targets V1-V3 RNA, the others the 16S RNA gene, V4 or V3-V4) and platform. Look at the example files to see how the study_metadata file should be setup.
── Column specification ──────────────────────────────────────────────
cols(
.default = col_character(),
read_length_bp = col_double(),
samples = col_double()
)
ℹ Use `spec()` for the full column specifications.
loading phyloseq objects: 1.737 sec elapsed
Filtering.
Filtering optionally filters samples and taxa (on the basis of taxonomy and of a prevalence and abundance filter) and returns the filtered phyloseq objects in a separate list. Taxonomic agglomeration is optionally carried out. The options for filtering are those defined in the options section.
Filter samples.
Samples with less than a given number of sequences are discarded first. As a consequence, a given phyloseq object may fall out below the set minimum of samples. Therefore the number of samples per object is checked again.
if(keep_time) tic("filter and glom samples")
# first prune objects with less than min_seqs sequences
physeq_list_0 <- physeq_list
# create first step of the filtering report
filtering_report <- vector("list", length = length(physeq_list))
filtering_report_0 <- map(physeq_list, report_step_0)
for(i in seq_along(physeq_list)){
cat("processing ", i, " of ", length(physeq_list), "\n")
# set plot_ecdf=F for faster execution
physeq_list_0[[i]] <- prune_samples_by_size(physeq_list[[i]],
names(physeq_list)[i],
plot_ecdf = F,
minseqs = min_seqs)
}
processing 1 of 5
processing 2 of 5
processing 3 of 5
processing 4 of 5
processing 5 of 5
cat("...done pruning samples...\n")
...done pruning samples...
# now recheck if all objects have enough samples
physeq_list_1 <- rem_low_sample_obj(physeq_list_0, ms = min_samples)
# create second step of the filtering report
stage_name = "prune samples"
for(i in seq_along(physeq_list_0)){
if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
name <- names(physeq_list_0)[i]
stage <- report_step_n(my_physeq = physeq_list_1[[name]],
my_physeq_o = physeq_list_0[[i]],
stage_name = stage_name)
} else {
stage <- c(stage_name,
samples = NA_real_,
sequences = NA_real_,
taxa = NA_real_,
prop_samples = NA_real_,
prop_seq = NA_real_,
prop_taxa = NA_real_)
}
filtering_report[[i]] <- rbind(stage_0 = as.character(filtering_report_0[[i]]), stage_1 =stage)
names(filtering_report)[i] <- names(physeq_list_0)[i]
}
if(verbose_output) print(filtering_report)
$ST106_FMBN_ps
stage samples sequences taxa prop_samples prop_seq
stage_0 "original" "30" "128735" "56" "1" "1"
stage_1 "prune samples" "30" "128735" "56" "1" "1"
prop_taxa
stage_0 "1"
stage_1 "1"
$ST110_FMBN_ps
stage samples sequences taxa prop_samples prop_seq
stage_0 "original" "112" "2056840" "90" "1" "1"
stage_1 "prune samples" "112" "2056840" "90" "1" "1"
prop_taxa
stage_0 "1"
stage_1 "1"
$ST115_FMBN_ps
stage samples sequences taxa prop_samples prop_seq
stage_0 "original" "63" "2346838" "45" "1" "1"
stage_1 "prune samples" "63" "2346838" "45" "1" "1"
prop_taxa
stage_0 "1"
stage_1 "1"
$ST131_FMBN_ps
stage samples sequences taxa prop_samples prop_seq
stage_0 "original" "108" "734976" "302" "1" "1"
stage_1 "prune samples" "107" "734493" "302" "1" "1"
prop_taxa
stage_0 "1"
stage_1 "1"
$ST136_FMBN_ps
stage samples sequences taxa prop_samples prop_seq
stage_0 "original" "47" "2759593" "60" "1" "1"
stage_1 "prune samples" "47" "2759593" "60" "1" "1"
prop_taxa
stage_0 "1"
stage_1 "1"
if(play_audio) beep(sound = 6)
if(keep_time) toc()
filter and glom samples: 0.767 sec elapsed
Taxonomic filtering.
A number of taxonomic filtering operations is performed at this stage (depending on the nature of the object and filtering options, set in the setting_options chunk). Plots are optionally produced and saved.
if(keep_time) tic("taxonomic filtering, step 1")
# check the nature of the taxonomic table
# in FMBN you either have ASV tables or tax tables with agglomeration at the species level or above
# which are the names of the tax levels?
tax_levels <- c("domain","phylum","class","order","family","genus","species")
# get/set rank names (this is necessary because phyloseq objects from FMBN or from the
# bioconductor pipeline with SILVA have different names)
physeq_list_2 <- lapply(physeq_list_1, gset_rank_names, tax_levels = tax_levels)
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
no change in rank names necessary
# remove uncharacterized taxa in all phyloseq objects (using a functional)
physeq_list_3 <- physeq_list_2
if(rm_unchar){
physeq_list_3 <- lapply(physeq_list_2, subset_taxa, !is.na(domain) & !domain %in% c("", "uncharacterized"))
physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(phylum) & !phylum %in% c("", "uncharacterized"))
}
# optionally remove Eukaryotes
if(rm_euk) {
physeq_list_3 <- lapply(physeq_list_3, subset_taxa, domain !="Eukaryota")
}
# optionally remove chloroplasts and mitochondria
if(rm_chlmit) {
physeq_list_3 <- lapply(physeq_list_3, remove_Chl_Mit)
}
# use lookup table to change taxonomy of Lactobacillus; necessary for phyloseq objects produced with SILVA
# but not for objects extracted from FMBN (which are transformed before extraction); will also change the
# taxa names for ASVs
# MAY BE SLOW
if(use_lookup){
# loop over the list of phyloseq objects
for(i in seq_along(physeq_list_3)){
# check the length of the names of the taxa; if <100 it is from FMBN, break
if(mean(sapply(taxa_names(physeq_list_3[[i]]), nchar),na.rm = T)<100){
next
} else {
# change the names
tnames <- str_c("ASV",seq(1:ntaxa(physeq_list_3[[i]])))
taxa_names(physeq_list_3[[i]])<-tnames
# get species to change
taxa_table <- as.data.frame(as(tax_table(physeq_list_3[[i]]),"matrix"))
taxa_table <- taxa_table %>% mutate(id = str_c(genus, species, sep = " "))
taxa_table <- left_join(taxa_table, lookup_table)
n_changes <- sum(!is.na(taxa_table$new_species))
taxa_table <- taxa_table %>% mutate(species = if_else(!is.na(new_species), new_species, species),
genus = if_else(!is.na(new_genus), new_genus, genus)
)
# remove columns
taxa_table <- dplyr::select(taxa_table, domain:species)
# now change genus for Lactobacillus with no species
to_change_lb <- which((taxa_table$genus == "Lactobacillus") & is.na(taxa_table$species))
taxa_table$genus[to_change_lb]<-"Lactobacillus complex"
# replace Leuconostocaceae with Lactobacillaceae
n_leuc <- nrow(dplyr::filter(taxa_table, family == "Leuconostocaceae"))
taxa_table <- taxa_table %>% mutate(family = ifelse(family == "Leuconostocaceae", "Lactobacillaceae", family))
taxa_table <- as.matrix(taxa_table)
rownames(taxa_table)<-tnames
tax_table(physeq_list_3[[i]])<-taxa_table
if(verbose_output){
cat(names(physeq_list_3)[i],": changed ", n_changes+n_leuc, " taxa\n", sep ="")
}
}
}
}
# remove further taxa which are uncharacterized at the family to class level
# note for self: might improve it by setting the level at or above which uncharacterized taxa can be removed
# rather than using a T/F flag
if(above_genus_flag){
physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(class) & !class %in% c("", "uncharacterized")) # Class
physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(order) & !order %in% c("", "uncharacterized")) # Order
physeq_list_3 <- lapply(physeq_list_3, subset_taxa, !is.na(family) & !family %in% c("", "uncharacterized")) # Family
}
# optionally perform taxonomic agglomeration,
# may take some time; can be made faster with plyr functions using parallelization or with furrr
physeq_list_4 <- physeq_list_3
if(taxglom != "none"){
physeq_list_4 <- lapply(physeq_list_3, tax_glom_name_change, taxa_glom = taxglom)
}
# create third step of the filtering report
stage_name = "taxonomic filter+glom"
for(i in seq_along(physeq_list_0)){
if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
name <- names(physeq_list_0)[i]
stage <- report_step_n(my_physeq = physeq_list_4[[name]],
my_physeq_o = physeq_list_0[[i]],
stage_name = stage_name)
} else {
stage <- c(stage_name,
samples = NA_real_,
sequences = NA_real_,
taxa = NA_real_,
prop_samples = NA_real_,
prop_seq = NA_real_,
prop_taxa = NA_real_)
}
filtering_report[[i]] <- rbind(filtering_report[[i]], stage_3 =stage)
}
if(keep_time) toc()
taxonomic filtering, step 1: 17.067 sec elapsed
if(keep_time) tic("calculate diversity pre-filter")
# Calculate diversity prior to filtering for prevalence and abundance
div_est_prefilter <- map_dfr(physeq_list_4, phyloseq::estimate_richness, split = F, measure=c("Observed","Chao1","Shannon"))
The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.
div_est_prefilter <- mutate(div_est_prefilter, Pielou_J = Shannon/log(Observed))
# calculate and add ave Bray-Curtis dissimilarity
meanbcdist <- map(physeq_list_4, phyloseq::distance, method="bray")
div_est_prefilter$ave_BC <- unlist(map(meanbcdist, mean))
row.names(div_est_prefilter) <- names(physeq_list_4)
# save for further use
save(div_est_prefilter,
file = paste(file.path(output_folder,out_filename_pref), "_divprefilter.Rdata",sep=""))
if(keep_time) toc()
calculate diversity pre-filter: 0.22 sec elapsed
# Prevalence and abundance filter
if(keep_time) tic("taxonomic filtering, step 2")
# NOTE using a prevalence filter based on fraction may be wrong for studies with large
# number of samples, in which one might want to retain taxa which appear in >10 samples
physeq_list_5 <- physeq_list_4
# will be skipped if filterOTUs == F
node_stat_list <- vector("list", length = length(physeq_list_5))
# a list which will host node stats, like the prevab data, initially empty,
# if nothing is added at this stage, node stats will be added at a later stage
if(filterOTUs){
prev_ab_list <- vector("list", length = length(physeq_list_4))
# will host the lists with the results
for(i in seq_along(physeq_list_4)){
if(verbose_output) cat("prevalence and abundance filter, physeq ",i," of ",
length(physeq_list_4),"\n")
prev_ab_list[[i]] <- filter_by_prev_ab(
myphyseq = physeq_list_4[[i]],
name = names(physeq_list_4)[i]
)
names(prev_ab_list)[i]<-names(physeq_list_4)[i]
# save the processed phyloseq
physeq_list_5[[i]] <- prev_ab_list[[i]][[1]]
# save prev ab table
node_stat_list[[i]] <- prev_ab_list[[i]][[4]]
names(node_stat_list)[i] <- names(physeq_list_4)[i]
}
}
prevalence and abundance filter, physeq 1 of 5
Joining, by = "label"
Saving 7 x 7 in image
prevalence and abundance filter, physeq 2 of 5
prevalence and abundance filter, physeq 3 of 5
prevalence and abundance filter, physeq 4 of 5
prevalence and abundance filter, physeq 5 of 5
# optionally save the prev_ab_list
if(save_prev_ab_list) save(prev_ab_list,
file = paste(file.path(output_folder,out_filename_pref), name, "_prevabl.Rdata",sep=""))
# create fourth step of the filtering report
stage_name = "prevalence and abundance"
for(i in seq_along(physeq_list_0)){
if(names(physeq_list_0)[i] %in% names(physeq_list_1)){
name <- names(physeq_list_0)[i]
stage <- report_step_n(my_physeq = physeq_list_5[[name]],
my_physeq_o = physeq_list_0[[i]],
stage_name = stage_name)
} else {
stage <- c(stage_name,
samples = NA_real_,
sequences = NA_real_,
taxa = NA_real_,
prop_samples = NA_real_,
prop_seq = NA_real_,
prop_taxa = NA_real_)
}
filtering_report[[i]] <- rbind(filtering_report[[i]], stage_4 =stage)
}
# remove unneeded objects
rm(physeq_list_1, physeq_list_3, physeq_list_4, prev_ab_list, taxa_table)
# create a data frame with the report
filtering_report_df <- map_dfr(filtering_report, as.data.frame, .id = "dataset")
filtering_report_df <- filtering_report_df %>%
mutate(data_type = if_else(str_detect(dataset, "FMBN"), "FMBN", "ASV")) %>%
mutate(dplyr::across(.cols = samples:prop_taxa, as.numeric)) %>%
mutate(stage = as_factor(stage))
if(verbose_output) {
print(filtering_report_df)
print(filtering_report_df %>%
dplyr::filter(stage != "original" & stage != "prune samples") %>%
ggplot(mapping = aes(x = data_type, y = prop_taxa)) +
facet_wrap(~stage) +
geom_boxplot() +
labs(x = "data type", y = "prop. taxa left"))
summ_filtering <- filtering_report_df %>%
group_by(data_type, stage) %>%
summarize(min_seq = min(prop_seq),
max_seq = max(prop_seq),
min_taxa = min(prop_taxa),
max_taxa = max(prop_taxa))
print(summ_filtering)
}

if(play_audio) beep(sound = 6)
if(keep_time) toc()
taxonomic filtering, step 2: 8.493 sec elapsed
The reduction in number of “taxa” is dramatic in most cases (a proportion from 0.010 to 0.21 left), while the proportion of sequences left is between 0.984 and 0.999.
Inferring the networks.
I will now try Microbial Association Network inference, with the methods and parameters specified in the options chunk. Separate lists will be generated for each method.
An error trapping routine has been implemented: if MAN estimation fails a try-error object rather than an object of class microNet will be returned.
All the returned objects will be saved in a list (1 slot for each phyloseq object, each with 1 slot for each inference method).
if(keep_time) tic("Microbial association network inference")
if(verbose_output) cat("Please be patient, this will take a while...\n")
Please be patient, this will take a while...
# list for results, 1 slot for each object
MAN_inf_results <- vector("list", length = length(physeq_list_5))
# create list for methods, 1 slot for each method
inf_meth_list <- vector("list", length = length(inf_methods))
for(i in seq_along(physeq_list_5)){
name <- names(physeq_list_5)[i]
if(verbose_output) cat("\n", "Inferring network(s) for ", name, ", ", i, "of ",
length(physeq_list_5), "\n", sep=" ")
if(keep_time) {
ticmessage_object <- paste("microbial association network inference, ",
name, ", ", i, " of ", length(physeq_list_5), sep = "")
tic(ticmessage_object)
}
for(j in seq_along(inf_methods)){
if(keep_time){
ticmessage_method <- paste("\n", "inference with method ",
inf_methods[j], ", ", j, " of ", length(inf_methods), sep = "")
tic(ticmessage_method)
}
infmethod <- inf_methods[[j]]
infparam <- inf_methods_param[[j]]
# infrence is carried out here using a user defined function loaded with the `source()` command
# you do not need to provide much detail, everything is taken care of in the options
# of course could be customized in the function call
inf_meth_list[[j]] <- infer_MAN(myphyseq = physeq_list_5[[i]],
inf_method = infmethod,
method_parameters = infparam)
names(inf_meth_list)[j] <- infmethod
if(keep_time) toc()
}
MAN_inf_results[[i]] <- inf_meth_list
names(MAN_inf_results)[i] <- name
if(keep_time) toc()
}
Inferring network(s) for ST106_FMBN_ps , 1 of 5
Infos about changed arguments:
Sparsification included in 'spieceasi'.
12 taxa and 30 samples remaining.
Optimal lambda may be larger than the supplied values
inference with method spieceasi, 1 of 4: 133.09 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.
12 taxa and 30 samples remaining.
1 job had warning: "There are variables in the data that have only zeros or only the same values."Optimal lambda may be larger than the supplied values
inference with method spring, 2 of 4: 90.672 sec elapsed
12 taxa and 30 samples remaining.
inference with method sparcc, 3 of 4: 1.403 sec elapsed
12 taxa and 30 samples remaining.
Feature(s) Halomonas, Loigolactobacillus, Tetragenococcus have more zeros than the threshold of 22 zeros. Excluding from output (will still be used in normalizing.)All-zero subjects generated by permutation: 16. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 5. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 12. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 5. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 28. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 21. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 29. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 19. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 10. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 8. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 8. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 26. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 4. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 26. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 10. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 7. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 25. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 14. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 9. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 4. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 17. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 30. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 18. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 9. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 30. These will be replaced by all zeros after normalizationAll-zero subjects generated by permutation: 1. These will be replaced by all zeros after normalizationAssociation matrix contains NAs (replaced by zeros).
inference with method ccrepe, 4 of 4: 0.983 sec elapsed
microbial association network inference, ST106_FMBN_ps, 1 of 5: 226.151 sec elapsed
Inferring network(s) for ST110_FMBN_ps , 2 of 5
Infos about changed arguments:
Sparsification included in 'spieceasi'.
14 taxa and 112 samples remaining.
inference with method spieceasi, 1 of 4: 133.038 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.
14 taxa and 112 samples remaining.
inference with method spring, 2 of 4: 80.62 sec elapsed
14 taxa and 112 samples remaining.
inference with method sparcc, 3 of 4: 1.835 sec elapsed
14 taxa and 112 samples remaining.
inference with method ccrepe, 4 of 4: 3.845 sec elapsed
microbial association network inference, ST110_FMBN_ps, 2 of 5: 219.341 sec elapsed
Inferring network(s) for ST115_FMBN_ps , 3 of 5
Infos about changed arguments:
Sparsification included in 'spieceasi'.
8 taxa and 63 samples remaining.
Optimal lambda may be larger than the supplied values
inference with method spieceasi, 1 of 4: 159.202 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.
8 taxa and 63 samples remaining.
Optimal lambda may be larger than the supplied values
inference with method spring, 2 of 4: 85.27 sec elapsed
8 taxa and 63 samples remaining.
inference with method sparcc, 3 of 4: 1.215 sec elapsed
8 taxa and 63 samples remaining.
Feature(s) Staphylococcus have more zeros than the threshold of 54 zeros. Excluding from output (will still be used in normalizing.)Association matrix contains NAs (replaced by zeros).
inference with method ccrepe, 4 of 4: 1.011 sec elapsed
microbial association network inference, ST115_FMBN_ps, 3 of 5: 246.699 sec elapsed
Inferring network(s) for ST131_FMBN_ps , 4 of 5
Infos about changed arguments:
Sparsification included in 'spieceasi'.
29 taxa and 107 samples remaining.
Optimal lambda may be larger than the supplied values
inference with method spieceasi, 1 of 4: 136.737 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.
29 taxa and 107 samples remaining.
47 jobs had warning: "'nearPD()' did not converge in 100 iterations"Optimal lambda may be larger than the supplied values
inference with method spring, 2 of 4: 606.343 sec elapsed
29 taxa and 107 samples remaining.
inference with method sparcc, 3 of 4: 2.445 sec elapsed
29 taxa and 107 samples remaining.
Feature(s) Acetitomaculum, Christensenellaceae_R-7_group, Clostridium, Corynebacterium, Enterococcus, Facklamia, Iamia, Lachnospiraceae_NK3A20_group, Loigolactobacillus, Luteimonas, NK4A214 group, Nocardioides, Paeniclostridium, Phascolarctobacterium, Planctomicrobium, Pseudomonas, Rikenellaceae_RC9_gut_group, Romboutsia, Ruminococcus, Staphylococcus, Treponema, Truepera, UCG-005 have more zeros than the threshold of 98 zeros. Excluding from output (will still be used in normalizing.)Association matrix contains NAs (replaced by zeros).
inference with method ccrepe, 4 of 4: 1.791 sec elapsed
microbial association network inference, ST131_FMBN_ps, 4 of 5: 747.318 sec elapsed
Inferring network(s) for ST136_FMBN_ps , 5 of 5
Infos about changed arguments:
Sparsification included in 'spieceasi'.
11 taxa and 47 samples remaining.
inference with method spieceasi, 1 of 4: 150.238 sec elapsed
Infos about changed arguments:
Sparsification included in 'spring'.
11 taxa and 47 samples remaining.
inference with method spring, 2 of 4: 84.859 sec elapsed
11 taxa and 47 samples remaining.
inference with method sparcc, 3 of 4: 1.467 sec elapsed
11 taxa and 47 samples remaining.
inference with method ccrepe, 4 of 4: 0.99 sec elapsed
microbial association network inference, ST136_FMBN_ps, 5 of 5: 237.56 sec elapsed
# create a report
inference_report <- vector("list", length(MAN_inf_results))
for(i in seq_along(MAN_inf_results)){
inference_report[[i]]<-map_dfr(MAN_inf_results[[i]], class, .id = "method")
names(inference_report)[i]<-names(MAN_inf_results)[i]
}
inference_report_df <- bind_rows(inference_report, .id = "dataset")
# save the list and do some clean-up
save(MAN_inf_results, file = file.path(output_folder, paste(out_filename_pref,"_MANlist.Rdata")))
rm(inf_meth_list, inference_report)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Microbial association network inference: 1677.632 sec elapsed
Analyze the networks.
The inferred networks will be analyzed using NetCoMi::netAnalyze() and the resulting objects will be processed further.
Network, node and edge stats will be extracted to data frames/tibbles for further processing.
A few extra analyses will be carried out using package phyloseq to add diversity and evenness indices to the metadata.
if(keep_time) tic("calculate diversity post-filter")
# estimate diversity for each object of the physeq_list_5, returns a list with the results
# extract and put together with metadata
# will generate warnings
div_est_postfilter <- map_dfr(physeq_list_5, estimate_richness, split = F,
measures = c("Observed","Chao1","Shannon"), .id = "label")
The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.The data you have provided does not have
any singletons. This is highly suspicious. Results of richness
estimates (for example) are probably unreliable, or wrong, if you have already
trimmed low-abundance taxa from the data.
We recommended that you find the un-trimmed data and retry.
div_est_postfilter <- mutate(div_est_postfilter, Pielou_J = Shannon/log(Observed))
# calculate and add average Bray-Curtis dissimilarity
meanbcdist <- map(physeq_list_5, phyloseq::distance, method="bray")
div_est_postfilter$ave_BC <- unlist(map(meanbcdist, mean))
row.names(div_est_postfilter) <- names(physeq_list_5)
# save for further use
save(div_est_postfilter,
file = paste(file.path(output_folder,out_filename_pref), "_divpostfilter.Rdata",sep=""))
# an alternative could be to use it on div_est_prefilter
study_metadata <- left_join(study_metadata, div_est_postfilter)
Joining, by = "label"
if(keep_time) toc()
calculate diversity post-filter: 0.257 sec elapsed
# calculate network statistics with netAnalyze
if(keep_time) tic("Calculate network statistics")
# the list for the methods within the dataset
net_stats <- vector("list", length = length(MAN_inf_results))
# net_stat_results is a list, do the calculation for each of the datasets, all inference methods
for(i in seq_along(MAN_inf_results)){
name <- names(MAN_inf_results)[i]
if(verbose_output) cat("Calculating network stats for",name,"\n")
net_stat_results <- vector("list", length = length(MAN_inf_results[[i]]))
for(j in seq_along(MAN_inf_results[[i]])){
mthd <- names(MAN_inf_results[[i]])[j]
if(verbose_output) cat("method",mthd,"\n")
# consider reducing argument hubQuant to 0.75-0.90 default is 0.95),
net_stat_results[[j]] <- calculate_net_stats(MAN_inf_results[[i]][[j]])
}
names(net_stat_results)<-names(MAN_inf_results[[i]])
net_stats[[i]] <- net_stat_results
names(net_stats)[i]<-names(MAN_inf_results)[i]
}
Calculating network stats for ST106_FMBN_ps
method spieceasi
method spring
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE, :
Network is empty.
method sparcc
method ccrepe
Calculating network stats for ST110_FMBN_ps
method spieceasi
method spring
method sparcc
method ccrepe
Calculating network stats for ST115_FMBN_ps
method spieceasi
method spring
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE, :
Network is empty.
method sparcc
method ccrepe
Calculating network stats for ST131_FMBN_ps
method spieceasi
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE, :
Network is empty.
method spring
Error in netAnalyze(microNet_obj, centrLCC = TRUE, avDissIgnoreInf = FALSE, :
Network is empty.
method sparcc
method ccrepe
Calculating network stats for ST136_FMBN_ps
method spieceasi
method spring
method sparcc
method ccrepe
rm(net_stat_results, meanbcdist, mthd)
if(keep_time) toc()
Calculate network statistics: 0.54 sec elapsed
# extracting global network properties
if(keep_time) tic("Extraction global network properties")
# extracting the global network stats
global_props_list_a <-vector("list",length(net_stats))
global_props_list_l <-vector("list",length(net_stats))
global_props_list_all <-vector("list",length(inf_methods))
global_props_list_lcc <-vector("list",length(inf_methods))
for (i in seq_along(net_stats)){
dataset <- names(net_stats)[i]
for (j in seq_along(net_stats[[i]])){
if(class(net_stats[[i]][[j]])!= "microNetProps"){
next
} else{
nnodes <- sum(net_stats[[i]][[j]]$centralities$degree1>0)
ntaxa <- nrow(net_stats[[i]][[j]]$input$assoMat1)
nposedge <- sum(net_stats[[i]][[j]]$input$assoMat1[lower.tri(net_stats[[i]][[j]]$input$assoMat1)]>0)
nnegedge <- sum(net_stats[[i]][[j]]$input$assoMat1[lower.tri(net_stats[[i]][[j]]$input$assoMat1)]<0)
extra_prop_vector <- c(nnodes, ntaxa, nposedge, nnegedge)
names(extra_prop_vector) <- c("nnodes", "ntaxa", "nposedge", "nnegedge")
global_props_list_all[[j]] <- c(unlist(net_stats[[i]][[j]]$globalProps),extra_prop_vector)
global_props_list_lcc[[j]] <- c(unlist(net_stats[[i]][[j]]$globalPropsLCC),extra_prop_vector)
names(global_props_list_all)[j] <- names(global_props_list_lcc)[j]<- names(net_stats[[i]][j])
}
# create data frame with results
all_df <- bind_rows(global_props_list_all, .id = "method")
lcc_df <- bind_rows(global_props_list_lcc, .id = "method")
}
global_props_list_a[[i]] <- all_df
global_props_list_l[[i]] <- lcc_df
names(global_props_list_a)[i] <- names(global_props_list_l)[i] <- names(net_stats)[i]
}
# note that str_sub only works if you have <=9 inference methods in inf_methods
global_all_df <- bind_rows(global_props_list_a, .id = "dataset")
global_lcc_df <- bind_rows(global_props_list_l, .id = "dataset")
global_all_df <- left_join(global_all_df,
select(study_metadata,label, obj_type, target,
region, platform, samples, type, Observed,
Chao1, Shannon, Pielou_J, ave_BC),
by = c("dataset" = "label")) %>%
mutate(across(where(is.numeric), ~na_if(.,Inf)))
global_lcc_df <- left_join(global_lcc_df,
select(study_metadata,label, obj_type, target,
region, platform, samples, type, Observed,
Chao1, Shannon, Pielou_J, ave_BC),
by = c("dataset" = "label")) %>%
mutate(across(where(is.numeric), ~na_if(.,Inf)))
# both might contain Inf which are replaced by NA (using dplyr::na_if()) to be handled
# correctly if doing PCA by pairwise deletion.
# the problem only occurs in avPath1 and clustCoef1, but I am handling it with a scoped mutate
# a better solution might be the use of hablar::rationalize()
# save the data frames for further use
write_tsv(global_all_df, file = paste(file.path(output_folder,out_filename_pref), "_netpropall.txt",sep=""))
write_tsv(global_lcc_df, file = paste(file.path(output_folder,out_filename_pref), "_netproplcc.txt",sep=""))
# print a summary table
global_all_df
rm(global_props_list_a, global_props_list_l, global_props_list_all,
global_props_list_lcc, all_df, lcc_df, nnodes, ntaxa, nposedge, nnegedge, extra_prop_vector)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Extraction global network properties: 0.932 sec elapsed
Global network properties have been saved as data frames for further use. They will be used together those generated for the other data sets.
Node properties.
Node properties can be extracted from the microNetProps objects and saved for further use.
# extract node properties
# using a loop takes slightly longer that using functionals but handles names better
# note that when using ASVs comparing nodes between datasets does not make much sense
if(keep_time) tic("Extracting node properties")
node_stats <- vector("list", length = length(net_stats))
for (i in seq_along(net_stats)) {
node_properties <- node_stat_list[[i]]
if(verbose_output) cat("extracting node stats for", names(net_stats)[i],"\n")
for (j in seq_along(net_stats[[i]])) {
if (class(net_stats[[i]][[j]]) == "microNetProps") {
node_stats[[i]][[j]] <- extract_node_stats(net_stat_list = net_stats[[i]][[j]],
nodestat = node_properties)
method <- names(net_stats[[i]])[j]
dataset <- names(net_stats)[i]
nrows <- nrow(node_stats[[i]][[j]])
node_stats[[i]][[j]] <- bind_cols(
dataset = rep(dataset,nrows),
method = rep(method,nrows),
node_stats[[i]][[j]]
)
} else {
cat("no node stats to return for",
names(net_stats)[i],
names(node_stats[[i]])[j],
"\n")
next
}
}
node_stats[[i]]<-bind_rows(node_stats[[i]])
}
extracting node stats for ST106_FMBN_ps
Joining, by = "label"
no node stats to return for ST106_FMBN_ps
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST110_FMBN_ps
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST115_FMBN_ps
Joining, by = "label"
no node stats to return for ST115_FMBN_ps
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST131_FMBN_ps
no node stats to return for ST131_FMBN_ps
no node stats to return for ST131_FMBN_ps
Joining, by = "label"
Joining, by = "label"
extracting node stats for ST136_FMBN_ps
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
Joining, by = "label"
node_stats_df <- bind_rows(node_stats)
# perform some tidying
node_stats_df <- node_stats_df %>%
tidyr::separate(dataset, into = c("Study", "Accn_n", "suf"), sep = "_", remove = F) %>%
dplyr::select(-Accn_n, -suf) %>%
mutate(label2 = if_else(!str_detect(dataset, "FMBN"),str_c(label, Study, sep = "_"), label))
# label2 is only necessary when using ASVs or OTUs, not if there has been taxonomic agglomeration
# consider removing the mutate instruction
write_tsv(node_stats_df, file = paste(file.path(output_folder,out_filename_pref), "_nodestats_df.txt",sep=""))
rm(node_stats)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Extracting node properties: 0.725 sec elapsed
Edge properties.
Edges and edge properties can also be extracted for further use. Important edge properties are:
It is also convenient to compare edges among different graphs (and to do so it might be convenient to make sure that “to” and “from” are always in alphabetical order, which is always true within the same graph but might not be necessarily true among different graphs).
The following chunk will (optionally):
integrate node (calculated with netAnalyse()) in the tidygraph list,
calculate edge betweenness,
compare networks (within dataset) by producing and saving Venn diagrams of the edges
Finally, a data frame with all edges will be produced for further use.
if(keep_time) tic("List with tidygraph objects created")
# creating a tidygraph object for each net
tidygraph_list <- vector("list", length = length(MAN_inf_results))
for(i in seq_along(MAN_inf_results)){
if(verbose_output) cat("Converting in tidygraphs for", names(MAN_inf_results)[i],"\n")
# the warning, if any, is not very informative, should consider passing names of datasets and methods
tidygraph_list[[i]] <- MAN_inf_results[[i]] %>% map(microNet_to_tidygraph, fail_w_err = F, use_asso_matrix = T)
}
Converting in tidygraphs for ST106_FMBN_ps
Joining, by = c("from", "to")
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST110_FMBN_ps
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST115_FMBN_ps
Joining, by = c("from", "to")
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST131_FMBN_ps
the network has 0 edges
the network has 0 edges
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Converting in tidygraphs for ST136_FMBN_ps
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
Joining, by = c("from", "to")
names(tidygraph_list)<-names(MAN_inf_results)
if(keep_time) toc()
List with tidygraph objects created: 0.997 sec elapsed
# optionally merge further node stats (depends on merge_n_stats)
# and calculate edge betweenness (depends on calc_e_betw)
# I am using a loop
if(keep_time) tic("Stats added to tidygraphs, edge dataframe created")
tidygraph_list_wstats <- vector("list", length = length(tidygraph_list))
# the list with the edge data frames
edge_list <- vector("list", length = length(tidygraph_list))
for(i in seq_along(tidygraph_list_wstats)){
# need to be reset
inner_tgl <- vector("list", length = length(tidygraph_list[[i]]))
inner_el <- vector("list", length = length(tidygraph_list[[i]]))
dtst <- names(tidygraph_list)[i]
for(j in seq_along(inner_tgl)){
if(is.tbl_graph(tidygraph_list[[i]][[j]])){
mthd = names(tidygraph_list[[i]])[j]
nstats <- node_stats_df %>% dplyr::filter(dataset == dtst & method == mthd)
inner_tgl[[j]] <- merge_stats(tg = tidygraph_list[[i]][[j]],
node_stats = nstats,
ebetw = calc_e_betw)
# extract edge tibble
inner_el[[j]] <- inner_tgl[[j]] %>%
activate(edges) %>%
as_tibble() %>%
mutate(method = mthd)
# do naming
names(inner_tgl)[j] <- names(inner_el)[j] <- names(tidygraph_list[[i]])[j]
} else {
next
}
}
tidygraph_list_wstats[[i]] <- inner_tgl
edge_list[[i]] <- bind_rows(inner_el)
edge_list[[i]] <- edge_list[[i]] %>%
mutate(dataset = dtst) %>% dplyr::select(dataset, method, !(dataset:method))
names(tidygraph_list_wstats)[i] <- names(edge_list)[i] <- names(tidygraph_list)[i]
}
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
Joining, by = "name"
rm(nstats)
# build and fix the edge df
edge_list_df <- bind_rows(edge_list)
edge_list_df <- edge_list_df %>%
mutate(name_from_sorted = if_else(from_name < to_name, from_name, to_name),
name_to_sorted = if_else(from_name < to_name, to_name, from_name)) %>%
mutate(edge_name = str_c(name_from_sorted, name_to_sorted, sep = "--"))
write_tsv(edge_list_df, file = paste(file.path(output_folder,out_filename_pref), "_edgelist_df.txt",sep=""))
if(keep_time) toc()
Stats added to tidygraphs, edge dataframe created: 1.145 sec elapsed
# make Venn plots
# I am using a loop
if(do_Venn){
if(keep_time) tic("Venn plots created and saved")
Venn_list <- vector("list", length(tidygraph_list))
for(i in seq_along(tidygraph_list)){
# need to select only elements of class tbl_graph
tblgrphs <- map_lgl(tidygraph_list[[i]], is.tbl_graph)
names(Venn_list) <- names(tidygraph_list)
if(length(tidygraph_list[[i]][tblgrphs])>1){
inner_list <- map(tidygraph_list[[i]][tblgrphs], function(x) as_ids(E(x)))
Venn_title <- names(tidygraph_list)[i]
Venn_file <- paste(file.path(output_folder,out_filename_pref),"_",Venn_title, "_venns.tiff",sep="")
my_fill <- (2:5)[1:length(tidygraph_list[[i]][tblgrphs])]
Venn_list[[i]] <- venn.diagram(inner_list,
fill = my_fill,
alpha = 0.3,
filename = Venn_file,
margin = 0.05,
main = Venn_title,
main.cex = 1.5,
main.fontface = "bold",
main.fontfamily = "sans",
main.pos = c(0.5, 1.05)
)
}
}
rm(inner_list, Venn_title, Venn_file)
if(keep_time) toc()
}
Venn plots created and saved: 3.538 sec elapsed
if(play_audio) beep(sound = 6)
Looking at the Venns it can be seen that the sparsest networks are almost always produced by SPIEC-EASI and that the number of edges shared by all methods for any given is very low.
Comparing global properties.
Comparing global network properties may be of assistance in evaluating the effect of the inference method or of the type of study. Although NetCoMi offers very effective tools to compare two networks, it does not easily generalize to more than 2-3 networks. Here, I will use the global_all_df and global_lcc_df and produce graphs using PCA.
Note line 1051 should be manually adapted to show all loadings and scores
if(keep_time) tic("Comparing global properties")
# create a label and extract matrix for the analysis
global_all_df_2 <- global_all_df %>%
tidyr::separate(dataset, into = c("study", "type", "object"), remove = F) %>%
select(-type, -object) %>%
mutate(method_brief = case_when(
method == "spieceasi" ~ "spi",
method == "spring" ~ "spr",
method == "ccrepe" ~ "ccr",
method == "sparcc" ~ "spa"
)) %>%
tidyr::unite(label, study, method_brief, sep = "_", remove = F)
# keep only relevant columns and make a matrix
global_all_df_mat <- global_all_df_2 %>%
dplyr::select(label, nComp1:nnegedge, samples, Chao1, Pielou_J, ave_BC) %>%
column_to_rownames("label") %>% as.matrix()
# optionally obtain a scatterplot matrix
if(verbose_output) ggpairs(as.data.frame(global_all_df_mat), progress = F)

# look at the number of components (using correlation matrix)
var_to_use <- !(colnames(global_all_df_mat) %in% c("ncomp", "vertConnect1","edgeConnect1","ntaxa"))
psych::fa.parallel(global_all_df_mat[,c(1:5,8:14,17:18)], fa = "pc", n.iter = 100)
Matrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneMatrix was not positive definite, smoothing was doneThe estimated weights for the factor scores are probably incorrect. Try a different factor score estimation method.
Parallel analysis suggests that the number of factors = NA and the number of components = 2

PCA1 <- psych::principal(global_all_df_mat[,c(1:5,8:14,17:18)], nfactors = 2, rotate = "varimax")
Matrix was not positive definite, smoothing was done
PCA1
Principal Components Analysis
Call: psych::principal(r = global_all_df_mat[, c(1:5, 8:14, 17:18)],
nfactors = 2, rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix
RC1 RC2
SS loadings 5.24 3.26
Proportion Var 0.37 0.23
Cumulative Var 0.37 0.61
Proportion Explained 0.62 0.38
Cumulative Proportion 0.62 1.00
Mean item complexity = 1.3
Test of the hypothesis that 2 components are sufficient.
The root mean square of the residuals (RMSR) is 0.13
with the empirical chi square 60.72 with prob < 0.59
Fit based upon off diagonal values = 0.9
biplot(PCA1, xlim.s = c(-1,4), ylim.s = c(-2,5), main = "PCA biplot, all variables")

fullnetscores <- cbind(global_all_df_2,PCA1$scores)
# the score plot
ggplot(fullnetscores, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
geom_point() +
labs(title = "all variables") +
scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
scale_color_brewer(type = "qual", palette = "Paired") +
theme(plot.title = element_text(hjust = 0.5))

var_to_use <- colnames(global_all_df_mat) %in% c("ncomp1", "modularity1","density1","clustCoef1",
"avPath1", "pep1", "Pielou_J", "ave_BC", "nnodes")
cat("variables to use: ", var_to_use, "\n")
variables to use: FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE TRUE TRUE
psych::fa.parallel(global_all_df_mat[,var_to_use], fa = "pc", n.iter = 100)
The estimated weights for the factor scores are probably incorrect. Try a different factor score estimation method.An ultra-Heywood case was detected. Examine the results carefully
Parallel analysis suggests that the number of factors = NA and the number of components = 2

PCA2 <- principal(global_all_df_mat[,var_to_use], nfactors = 2, rotate = "varimax")
PCA2
Principal Components Analysis
Call: principal(r = global_all_df_mat[, var_to_use], nfactors = 2,
rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix
RC1 RC2
SS loadings 2.66 2.28
Proportion Var 0.33 0.29
Cumulative Var 0.33 0.62
Proportion Explained 0.54 0.46
Cumulative Proportion 0.54 1.00
Mean item complexity = 1.2
Test of the hypothesis that 2 components are sufficient.
The root mean square of the residuals (RMSR) is 0.12
with the empirical chi square 15.18 with prob < 0.3
Fit based upon off diagonal values = 0.89
biplot(PCA2)

fullnetscores_2 <- cbind(global_all_df_2,PCA2$scores)
var_acc_RC1 <- round(PCA2$Vaccounted[4,1],3)
var_acc_RC2 <- round(PCA2$Vaccounted[4,2],3)
# the score plot
ggplot(fullnetscores_2, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
geom_point() +
labs(title = "selected variables") +
scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
scale_color_brewer(type = "qual", palette = "Paired") +
labs(x = paste("RC1 (", var_acc_RC1, ")", sep =""),
x = paste("RC2 (", var_acc_RC2, ")", sep ="")) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5))
ggsave(filename = paste(file.path(output_folder,out_filename_pref), "_PCA.tiff",sep=""), device = "tiff", width = 6,
height = 5, units = "in", dpi=600)

if(keep_time) toc()
Comparing global properties: 57.722 sec elapsed
With these many datasets and inference methods the PCA becomes difficult to use. Note how the properties of the network are, as expected, specific to any given inference methods and data-set and how methods based on correlation (sparCC and CCREPE) tend to be closer among them than to methods based on conditional dependence.
The analysis is repeated below on the largest connected component for each network.
if(keep_time) tic("Comparing global properties on LCC")
# create a label and extract matrix for the analysis
global_all_df_3 <- global_lcc_df %>%
tidyr::separate(dataset, into = c("study", "type", "object"), remove = F) %>%
select(-type, -object) %>%
mutate(method_brief = case_when(
method == "spieceasi" ~ "spi",
method == "spring" ~ "spr",
method == "ccrepe" ~ "ccr",
method == "sparcc" ~ "spa"
)) %>%
tidyr::unite(label, study, obj_type, method_brief, sep = "_", remove = F)
# keep only relevant columns and make matrix
global_all_df_mat_2 <- global_all_df_3 %>%
dplyr::select(label, lccSize1:nnegedge, samples, Chao1, Pielou_J, ave_BC) %>%
column_to_rownames("label") %>% as.matrix()
# need to remove an Inf value, I am removing the row
# obtain a scatterplot matrix
if(verbose_output) ggpairs(as.data.frame(global_all_df_mat_2[-3,]), progress = F)

# look at the number of components (using correlation matrix): must exclude
# ave path length and clustering coefficient which contain Inf and NaN
psych::fa.parallel(global_all_df_mat_2[-3,c(1:3,6,9:14,17:18)], fa = "pc", n.iter = 100)
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
In factor.stats, the correlation matrix is singular, an approximation is used
The estimated weights for the factor scores are probably incorrect. Try a different factor score estimation method.In factor.scores, the correlation matrix is singular, an approximation is used
I was unable to calculate the factor score weights, factor loadings used instead
Parallel analysis suggests that the number of factors = NA and the number of components = 1

PCA1_lcc <- psych::principal(global_all_df_mat_2[-3,c(1:3,6,9:14,17:18)], nfactors = 2, rotate = "varimax")
The determinant of the smoothed correlation was zero.
This means the objective function is not defined.
Chi square is based upon observed residuals.
The determinant of the smoothed correlation was zero.
This means the objective function is not defined for the null model either.
The Chi square is thus based upon observed correlations.
In factor.stats, the correlation matrix is singular, an approximation is used
The matrix is not positive semi-definite, scores found from Structure loadings
PCA1_lcc
Principal Components Analysis
Call: psych::principal(r = global_all_df_mat_2[-3, c(1:3, 6, 9:14,
17:18)], nfactors = 2, rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix
RC1 RC2
SS loadings 5.90 2.58
Proportion Var 0.49 0.21
Cumulative Var 0.49 0.71
Proportion Explained 0.70 0.30
Cumulative Proportion 0.70 1.00
Mean item complexity = 1.1
Test of the hypothesis that 2 components are sufficient.
The root mean square of the residuals (RMSR) is 0.13
with the empirical chi square 42.15 with prob < 0.51
Fit based upon off diagonal values = 0.93
biplot(PCA1_lcc, main = "PCA biplot, all variables")

fullnetscores_lcc <- cbind(global_all_df_3[-3,],PCA1_lcc$scores)
# the score plot
ggplot(fullnetscores_lcc, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
geom_point() +
labs(title = "all variables") +
scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
scale_color_brewer(type = "qual", palette = "Paired") +
theme(plot.title = element_text(hjust = 0.5))

var_to_use_lcc <- colnames(global_all_df_mat) %in% c("lccSize1", "modularity1","density1", "pep1", "Pielou_J", "ave_BC", "nnodes")
cat("variables to use: ", var_to_use_lcc, "\n")
variables to use: FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE TRUE TRUE
psych::fa.parallel(global_all_df_mat_2[-3,var_to_use_lcc], fa = "pc", n.iter = 100)
The estimated weights for the factor scores are probably incorrect. Try a different factor score estimation method.
Parallel analysis suggests that the number of factors = NA and the number of components = 1

PCA2_lcc <- principal(global_all_df_mat_2[-3,var_to_use_lcc], nfactors = 2, rotate = "varimax")
PCA2_lcc
Principal Components Analysis
Call: principal(r = global_all_df_mat_2[-3, var_to_use_lcc], nfactors = 2,
rotate = "varimax")
Standardized loadings (pattern matrix) based upon correlation matrix
RC1 RC2
SS loadings 2.14 2.12
Proportion Var 0.36 0.35
Cumulative Var 0.36 0.71
Proportion Explained 0.50 0.50
Cumulative Proportion 0.50 1.00
Mean item complexity = 1.2
Test of the hypothesis that 2 components are sufficient.
The root mean square of the residuals (RMSR) is 0.11
with the empirical chi square 6.1 with prob < 0.19
Fit based upon off diagonal values = 0.93
biplot(PCA2_lcc)

fullnetscores_2_lcc <- cbind(global_all_df_2[-3,],PCA2_lcc$scores)
# the score plot
var_acc_RC1_lcc <- round(PCA2$Vaccounted[4,1],3)
var_acc_RC2_lcc <- round(PCA2$Vaccounted[4,2],3)
# the score plot
ggplot(fullnetscores_2_lcc, mapping = aes(x = RC1, y = RC2, shape = method, colour = study)) +
geom_point() +
labs(title = "selected variables") +
scale_shape_manual(values = c("spieceasi" = 16, "spring" = 17, "ccrepe" = 15, "sparcc" = 18)) +
scale_color_brewer(type = "qual", palette = "Paired") +
labs(x = paste("RC1 (", var_acc_RC1_lcc, ")", sep =""),
x = paste("RC2 (", var_acc_RC2_lcc, ")", sep ="")) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5))
ggsave(filename = paste(file.path(output_folder,out_filename_pref), "_PCA_lcc.tiff",sep=""), device = "tiff", width = 6,
height = 5, units = "in", dpi=600)

if(keep_time) toc()
Comparing global properties on LCC: 73.565 sec elapsed
The results are similar, and there is not much to add. While SPIEC-EASI produces the most sparse networks (and can be a candidate for the detection of the most parsimonious interaction sets), CCREPE and SparCC both may detect indirect interactions and, detecting clusters in this networks may be useful to detect sub-biomes within a given study.
Plotting selected networks.
I will now use tidygraph and ggraph to plot the networks. The thickness of the edges is made proportional to the absolute value of the association measure. Copresence edges are in green, mutual exclusion ones in red. Size of the nodes is made proportional to the total degree (negative degree + positive degree). The color of the nodes is determined by the phylum. Some of this options can be adjusted in the call to the plot_ggraph() function below. Others must be adjusted in the code of the function (all functions are in the source folder).
if(keep_time) tic("Plotting the networks with ggraph")
# create a list of network plots
netplot_list <- vector("list", length = length(tidygraph_list_wstats))
for(i in seq_along(tidygraph_list_wstats)){
netplot_list_2 <- vector("list", length = length(tidygraph_list_wstats[[i]]))
dtst <- names(tidygraph_list_wstats)[i]
for(j in seq_along(tidygraph_list_wstats[[i]])){
if(!is.tbl_graph(tidygraph_list_wstats[[i]][[j]])){
netplot_list_2[[j]] <- "no plot to return"
next
} else {
tg <- tidygraph_list_wstats[[i]][[j]]
mthd <- names(tidygraph_list_wstats[[i]])[j]
# the default for the argument c0l0r is phylum, otherwise
# use c0l0r = clust_memb
# argument lp = "bottom" is the default; "right" is an alternative
netplot_list_2[[j]] <- plot_ggraph(tidy_graph = tg,
method = mthd,
name = dtst,
lp = "right")
names(netplot_list_2)[j] <- mthd
print(netplot_list_2[[j]])
}
}
netplot_list[[i]] <- netplot_list_2
names(netplot_list)[i] <- dtst
}
















rm(tg, dtst, mthd, netplot_list_2)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Plotting the networks with ggraph: 12.194 sec elapsed
Due to the large number of interactions it is hard to discuss the results. In some of the graphs clusters are clearly evident (and could be highlighted by using the cluster id for colors). The structure of the graphs obtained with the different methods for each given dataset is also different. This may be due to the fact that both the correlation based methods may detect indirect interactions.
In addition, given the scope of this analysis (with emphasis on nodes and edges conserved across different studies) there is little point in discussing individual networks, because this would require going into detail in the experimental approach used in each study.
The following chunk is designed to compare network plots built with different methods in a grid. Two of the datasets (chosen because they returned a network for all inference methods and because they had networks of different complexity have been chosen here). However, due to the large number of elements in the graph, plotting all the elements (graph, legend, title) is difficult and may require acting on graphic parameters.
if(keep_time) tic("Plotting and saving networks in a grid")
p1<-netplot_list[[2]][[1]]
p2<-netplot_list[[2]][[2]]
p3<-netplot_list[[2]][[3]]
p4<-netplot_list[[2]][[4]]
ggrid_1 <- egg::ggarrange(p1, p2, p3, p4, nrow = 2, ncol = 2)
fn <- paste(file.path(output_folder,out_filename_pref), "_ST110.tiff",sep="")
ggsave(ggrid_1, filename = fn, dpi = 600, width = 7, height = 7)
ggrid_1
p5<-netplot_list[[9]][[1]]
p6<-netplot_list[[9]][[2]]
p7<-netplot_list[[9]][[3]]
p8<-netplot_list[[9]][[4]]
ggrid_2 <- egg::ggarrange(p5, p6, p7, p8, nrow = 2, ncol = 2)
fn <- paste(file.path(output_folder,out_filename_pref), "_ST136.tiff",sep="")
ggsave(ggrid_2, filename = fn, dpi = 600, width = 7, height = 7)
ggrid_2
if(play_audio) beep(sound = 6)
if(keep_time) toc()
rm(p1, p2, p3, p4, p5, p6, p7, p8)
Node analysis.
When comparing networks from several studies it might be of interest to classify nodes on the basis of their measures of centrality across multiple studies: is there a super hub (i.e. a node which i a hub in several networks)? How are different centrality measures related? Are there nodes which engage more often in negative interactions? Of course it would be pointless to compare across several methods of inference or, given the results above, between ASV and FMBN studies (when aggregation at the genus level is used). This script will produce a few node plots as a proof of concept. Both SPIEC-EASI and SparCC will be used on data extracted from FMBN and aggregated at the genus level.
if(keep_time) tic("Node analysis")
mymethods <- c("spieceasi","sparcc")
n_to_label <- 20 # max number of nodes to label in the node plots
node_analysis_list <- vector("list", length = length(mymethods))
for(i in seq_along(mymethods)){
# create the dfs, one for spiec-easi and one for sparCC and only select FMBN
# only select nodes with degree>0
FMBN_node_df <- node_stats_df %>%
dplyr::filter(method == mymethods[i]) %>%
dplyr::filter(degree>0) %>%
mutate(Study = as.factor(Study),
rel_pos_degree = pos_degree / sum(pos_degree)) %>%
dplyr::rename(tlabel = label)
# use a loop to do the node degree graphs, print them and put them in a list
node_degree_plot_list <- vector("list", length=nlevels(FMBN_node_df$study))
for(j in seq_along(levels(FMBN_node_df$Study))){
gtitle = paste(levels(FMBN_node_df$Study)[j], mymethods[i], sep = ", ")
temp_df <- FMBN_node_df %>%
dplyr::filter(Study ==levels(FMBN_node_df$Study)[j]) %>%
mutate(dgr = pos_degree + neg_degree) %>%
arrange(-dgr) %>%
rowid_to_column() %>%
mutate(to_label = if_else((rowid<=20 | is_hub), tlabel, NA_character_))
ave_degree = mean(temp_df$dgr, na.rm = T)
ave_pos_degree = mean(temp_df$pos_degree, na.rm = T)
# creating the plot, only the names of the top 20 nodes (in terms of degree)
# are plotted, hubs are always plotted
ggp <- ggplot(
temp_df,
mapping = aes(
x = pos_degree,
y = dgr,
label = str_trunc(genus, 12, side = "center")
)
) +
geom_smooth(method = "lm", linetype = 3, se = F, color = I("black"), show.legend = F) +
geom_point(mapping = aes(color = phylum,
size = relAbundance,
alpha = between)) +
geom_text_repel(show.legend = F, max.overlaps = 20, alpha = I(0.5)) +
geom_abline(slope = 1, intercept = 0, linetype = 1) +
geom_hline(yintercept = ave_degree, linetype = 3, show.legend = F) +
geom_vline(xintercept = ave_pos_degree, linetype = 3, show.legend = F) +
scale_alpha_continuous(range = c(0.4, 1)) +
scale_size_continuous(range = c(1,6)) +
labs(
x = "positive degree",
y = "degree",
size = "relative abundance",
alpha = "betweenness",
title = gtitle
) +
theme_bw() +
theme(plot.title = element_text(hjust = 0.5))
print(ggp)
node_degree_plot_list[[j]] <- ggp
names(node_degree_plot_list[[j]]) <- gtitle
}
file_name <- paste(file.path(output_folder,out_filename_pref), "_", mymethods[i], "_nodeplots.Rdata",sep="")
save(node_degree_plot_list, file = file_name)
# calculate frequency for hubs (meaningful only if you have many taxa and the percentile for hub detection is low, say 0.75-0.90)
n_datasets <- dplyr::n_distinct(FMBN_node_df$dataset)
FMBN_node_df_hubs <- FMBN_node_df %>%
dplyr::filter(is_hub == T) %>%
group_by(tlabel, phylum, class) %>%
summarise(hub_freq = n()/n_datasets) %>%
arrange(-hub_freq)
cat("method", mymethods[i], "hub frequency", "\n")
head(FMBN_node_df_hubs, 20)
# save elements in the list
node_analysis_list[[i]] <- list(
node_df = FMBN_node_df,
plot_list = node_degree_plot_list,
node_df_hubs = FMBN_node_df_hubs
)
names(node_analysis_list)[i]<- mymethods[i]
}
method spieceasi hub frequency
method sparcc hub frequency









if(play_audio) beep(sound = 6)
if(keep_time) toc()
Node analysis: 10.47 sec elapsed
rm(FMBN_node_df, node_degree_plot_list, temp_df, FMBN_node_df_hubs)
The node plots provide a number of visual cues:
the horizontal and vertical dotted lines show the average value for degree and positive degree and may help in identifying nodes whose values are far from the mean;
the diagonal continuous line corresponds to points for which positive degree and degree are equal; points above the line have at least 1 negative interaction
the diagonal dotted line is the linear regression line for degree as a function of positive degree; its position and slope relative to the previous line indicates on average how much the ratio between total degree and positive degree changes as a function of degree; data points which are far from this line also have an unusual ratio between the two values compared to the other nodes in the same dataset.
negative hubs will be located close to the upper left corner; positive hubs will be closer to the right of the plot.
Even with SPIEC-EASI the number of plotted nodes is high for some studies (but very low for others), and one may consider plotting a given number of nodes (the ones with top degree? top eigenvector centrality?, some combination of both) to simplify the plot.
Said that, although betweenness centrality may be interesting to identify “bottleneck” taxa, looking at betweenness is difficult (the shades of alpha = transparency are visually difficult to separate), but otherwise degree, positive degree and abundance are all easy to visualise.
Edge analysis.
Edge analysis only makes sense once one has settled for meaningful questions and selected a large and diverse enough set of studies. Questions which might be relevant:
how stable is an edge?
given an inference method, can one assemble a consensus network using as weights the number of times a given edge occurs? The frequency of occurrence?
how important is an edge (as measured by edge betweenness)? One must choose a meaningful context for this
are co-occurrence relationships more frequent among members of the same class or family (and the reverse for mutual exclusion)? This only makes sense within a given method and must be corrected by the frequency of ech given larger taxon.
given a (possibly important) microorganism, with which microorganisms is it most frequently associated with positive or negative associations? This only makes sense within a given method and once one has analyzed a large number of studies
for moderately large networks, which is the cumulative distribution of edge betweenness? Does this hold any importance in terms of structure of the networks?
if(keep_time) tic("Edge analysis")
# this calculates the empirical quantile of edge betweenness
# by dataset and method: I suppose one can then select those >0.95
# to get a sort of hubness for edges, but it makes sense only for
# large number of edges
edge_list_df <- edge_list_df %>% group_by(dataset, method) %>%
mutate(ebq = ecdf(edge_betw)(edge_betw)) %>% ungroup()
# calculate the frequency of edges, by study and type and
# median value and IQR for IQR
# this only checks for conserved edges across methods
edge_freq_bystudy <- edge_list_df %>%
group_by(dataset, asso_type, edge_name) %>%
summarise(n = n(),
med_ebq = median(ebq),
iqr_ebq = IQR(ebq)) %>%
arrange(-n, -med_ebq, ) %>%
ungroup()
`summarise()` has grouped output by 'dataset', 'asso_type'. You can override using the `.groups` argument.
# I am now generating a plot may be with the top 50 edges, only for FMBN studies
edge_freq_bystudy_FMBN <- edge_freq_bystudy %>%
dplyr::filter(str_detect(dataset, "FMBN")) %>%
arrange(-n, -med_ebq)
# let's try a plot (gives an emphasis to most stable edges)
slice_to_plot <- slice(edge_freq_bystudy_FMBN, 1:50) %>%
mutate(edge_name = forcats::fct_reorder(edge_name, med_ebq))
ggplot(slice_to_plot,
mapping = aes(x = edge_name, y = med_ebq, size = n, color = asso_type)) +
geom_point() +
coord_flip() +
labs(x = "edge", y = "edge betweenness quantile",
size = "edge freq.") +
scale_colour_manual(values = c("green","red"))

# another way to see it, based on stability
slice_to_plot_2 <- slice(edge_freq_bystudy_FMBN, 1:50) %>%
mutate(edge_name = forcats::fct_reorder(edge_name, n))
ggplot(slice_to_plot_2,
mapping = aes(x = edge_name, y = n, size = med_ebq, color = asso_type)) +
geom_point() +
coord_flip() +
labs(x = "edge", y = "edge freq.",
size = str_wrap("edge betweenness quantile", 15)) +
scale_colour_manual(values = c("green","red"))
fn <- paste(file.path(output_folder,out_filename_pref), "_edge_n.tiff",sep="")
ggsave(filename = fn, dpi = 600, width = 10, height = 7)

Many of the most stable edges include gut bacteria and probably represent cluster of interactions from this environment which are carried over to milk and then, possibly, cheese (some studies analyzed here include also environmental samples, like feces).
The analysis here was carried out by groping by dataset and association type, therefore the frequency is by dataset and a frequency of 4 indicates that a given edge was detected by all 4 methods in a single dataset. The code can be easily modified to detect edges which are conserved across different studies and different methods. I will now try to estimate the taxonomic assortativity. To do this, the odds ratio (OR) for copresence links (given that the nodes belong to the same family) and mutual exlusion links (given that the nodes belong to different families) is calculated and the significance is evaluated using epi.2by2() function.
# same, by method and assotype, only for FMBN
# only meaningful for high number of studies
edge_freq_bymethod <- edge_list_df %>%
dplyr::filter(str_detect(dataset, "FMBN")) %>%
group_by(method, asso_type) %>%
count(edge_name) %>%
arrange(method, asso_type, -n)
# The following section evaluates taxonomic assortativity (i.e. evaluates if
# copresence associations are more frequent among members of the same
# family, order or class). The odds ratio of copresence relationships within
# the same family is calculated using epiR::epi.2by2()
# first, add taxonomy to the
unique_tax <- node_stats_df %>%
dplyr::select(label, domain:species) %>%
distinct()
edge_list_df_wtaxa <- edge_list_df
edge_list_df_wtaxa <- left_join(edge_list_df_wtaxa,
dplyr::select(unique_tax, label, class, order, family),
by = c("from_name" = "label")) %>%
dplyr::rename(from_class = class, from_order = order, from_family = family)
edge_list_df_wtaxa <- left_join(edge_list_df_wtaxa,
dplyr::select(unique_tax, label, class, order, family),
by = c("to_name" = "label")) %>%
dplyr::rename(to_class = class, to_order = order, to_family = family)
# check if same family or same class
edge_list_df_wtaxa <- edge_list_df_wtaxa %>%
mutate(same_class = if_else(from_class == to_class, T, F),
same_order = if_else(from_order == to_order, T, F),
same_family = if_else(from_family == to_family, T, F)
)
# use a loop to calculate odds ratios and probabilities
# get the number of studies
edge_list_df_wtaxa <- edge_list_df_wtaxa %>%
separate(dataset, into = c("study", "col2", "col3"), remove = F) %>%
select(-col2, -col3) %>%
mutate(study = as.factor(study),
sf = factor(same_family, levels = c("TRUE", "FALSE")),
so = factor(same_order, levels = c("TRUE", "FALSE")),
sc = factor(same_class, levels = c("TRUE", "FALSE"))
)
assort_results_list <- vector(mode = "list", length = nlevels(edge_list_df_wtaxa$study))
taxo_level_selection <- "family"
for(i in seq_along(levels(edge_list_df_wtaxa$study))){
input_df_study <- edge_list_df_wtaxa %>% dplyr::filter(study == levels(study)[i]) %>%
mutate(method = as.factor(method))
inner_result_list <- vector(mode = "list", length = nlevels(input_df_study$method))
for(j in seq_along(levels(input_df_study$method))){
input_df_method <- input_df_study %>%
dplyr::filter(method == levels(method)[j])
if(verbose_output) cat("\nProcessing", levels(edge_list_df_wtaxa$study)[i],
"method", levels(input_df_method$method)[j],"\n")
inner_result_list[[j]]<-try(odds_ratio(input_df_method, taxo_level = taxo_level_selection))
if(verbose_output & class(inner_result_list[[j]]) == "data.frame") {
cat("\nSuccess! Odds ratio estimated")
} else {
cat("\nFail: unable to return test results!")
}
names(inner_result_list)[j]<- levels(input_df_method$method)[j]
}
assort_results_list[[i]] <- inner_result_list
names(assort_results_list)[i] <- levels(edge_list_df_wtaxa$study)[i]
}
Processing ST106 method ccrepe
Success! Odds ratio estimated
Processing ST106 method sparcc
NaNs producedNaNs produced
Success! Odds ratio estimated
Processing ST106 method spieceasi
Error in if (any(x2)) upp[x2] <- rep(1, sum(x2)) :
missing value where TRUE/FALSE needed
Fail: unable to return test results!
Processing ST110 method ccrepe
Success! Odds ratio estimated
Processing ST110 method sparcc
Success! Odds ratio estimated
Processing ST110 method spieceasi
NaNs producedError in uniroot(function(or) { :
f() values at end points not of opposite sign
Fail: unable to return test results!
Processing ST110 method spring
NaNs producedNaNs produced
Success! Odds ratio estimated
Processing ST115 method ccrepe
Success! Odds ratio estimated
Processing ST115 method sparcc
Error in if (sum(A) > 0 & sum(B) > 0 & sum(C) > 0 & sum(D) > 0) { :
missing value where TRUE/FALSE needed
Fail: unable to return test results!
Processing ST115 method spieceasi
Error in if (any(x2)) upp[x2] <- rep(1, sum(x2)) :
missing value where TRUE/FALSE needed
Fail: unable to return test results!
Processing ST131 method ccrepe
NaNs producedError in uniroot(function(or) { :
f() values at end points not of opposite sign
Fail: unable to return test results!
Processing ST131 method sparcc
Error in if (sum(A) > 0 & sum(B) > 0 & sum(C) > 0 & sum(D) > 0) { :
missing value where TRUE/FALSE needed
Fail: unable to return test results!
Processing ST136 method ccrepe
Success! Odds ratio estimated
Processing ST136 method sparcc
NaNs producedNaNs producedNaNs producedNaNs produced
Success! Odds ratio estimated
Processing ST136 method spieceasi
NaNs producedError in uniroot(function(or) { :
f() values at end points not of opposite sign
Fail: unable to return test results!
Processing ST136 method spring
NaNs producedNaNs produced
Success! Odds ratio estimated
# clean-up the list and put together the data frames
df_list <- vector(mode = "list", length = length(assort_results_list))
for(i in seq_along(assort_results_list)){
df_list[[i]] <- df_return(assort_results_list[[i]])
names(df_list)[i]<-names(assort_results_list)[i]
}
assort_results_df <- bind_rows(df_list, .id = "study")
# create dummy ORest values by replacing Inf
assort_results_df <- assort_results_df %>%
mutate(OR_est_wdummy = if_else(OR_est==Inf, 99, OR_est)) %>%
mutate(lOR_est_wdummy = log(OR_est_wdummy),
significant = if_else(OR_p.value.1s <=0.05, T, F))
# plot, studies are pooled
ggplot(assort_results_df, mapping = aes(x = assort_test, y = lOR_est_wdummy, colour = significant)) +
facet_wrap(~method) +
geom_jitter(width = 0.2) +
labs(y = "log(odds ratio)") +
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# tabulating
xtabs(~ method + significant +assort_test , data = assort_results_df)
, , assort_test = cop_samefamily
significant
method FALSE TRUE
ccrepe 4 0
sparcc 2 1
spring 1 1
, , assort_test = mutE_difffamily
significant
method FALSE TRUE
ccrepe 4 0
sparcc 2 1
spring 1 1
rm(assort_results_list, input_df_study, inner_result_list, input_df_method, i, j, df_list)
if(play_audio) beep(sound = 6)
if(keep_time) toc()
Edge analysis: 25.936 sec elapsed
Even if the odds ratios are very high in some cases (actually Inf, due to division by 0) they are not significant. My conclusion is that there is not sufficient support for the occurrence of taxonomic assortativity (i.e. for a significantly higher probability of having a copresence link between members of the same family).
Similar results can be obtained when looking at the order and class level.
Citations and copyright.
Citations.
Citations for the metataxonomic studies used in this analysis are in the study_metadata data frame (if any). The main package used in this analysis is NetCoMi, and of course, Phyloseq. Citations for all packages are below.
Copyright notice.
Script created by Eugenio Parente (eugenio.parente@unibas.it), Università degli Studi della Basilicata, 2021 https://github.com/ep142 This script has been tested with R 4.0.5 and 4.1 on two different Apple computers with 8 GB RAM running MacOS 10.14.6.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LS0tCnRpdGxlOiAiSW5mZXJlbmNlIGFuZCBjb21wYXJpc29uIG9mIG1pY3JvYmlhbCBhc3NvY2lhdGlvbiBuZXR3b3JrcyB2MS4zIgpzdWJ0aXRsZTogIkluZmVycmluZyBuZXR3b3JrcyBmcm9tIEZNQk4gZGF0YSwgZ2VudXMgbGV2ZWw6IGV4YW1wbGUgd29ya2Zsb3ciCmF1dGhvcjogIkUuIFBhcmVudGUsIFNBRkUiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgojIEluc3RhbGwvbG9hZCBwYWNrYWdlcyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi5iaW9jX3BhY2thZ2VzIDwtIGMoIkJpb2NNYW5hZ2VyIiwgImdlbmVmaWx0ZXIiLCAicGh5bG9zZXEiKQouY3Jhbl9wYWNrYWdlcyA8LSBjKCJkZXZ0b29scyIsICJwYXJhbGxlbCIsICJ0aWR5dmVyc2UiLCAiaWdyYXBoIiwgCiAgICAgICAgICAgICAgICAgICAgIlZlbm5EaWFncmFtIiwgInRpZHlncmFwaCIsICJsb2JzdHIiLCAidGljdG9jIiwgImJlZXByIiwKICAgICAgICAgICAgICAgICAgICAicHN5Y2giLCAiR0dhbGx5IiwgImdncmVwZWwiLCAiZ2dyYXBoIiwgImVnZyIsICJlcGlSIikKLmdpdGh1Yl9wYWNrYWdlcyA8LSBjKCJTcGllY0Vhc2kiLCJOZXRDb01pIikKIyBsb2JzdHIgaXMgY2FsbGVkIGZvciBsb2JzdHI6Om9ial9zaXplKCkKIyBlZ2cgaXMgb25seSB1c2VkIHRvIGFycmFuZ2UgcGxvdHMgaW4gZ3JpZHMKCi5pbnN0IDwtIC5iaW9jX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKaWYoYW55KCEuaW5zdCkpIHsKICBpZighLmluc3RbMV0pIHsKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICAgIC5pbnN0IDwtIC5iaW9jX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKICAgIH0KICBpZihhbnkoIS5pbnN0WzI6bGVuZ3RoKC5pbnN0KV0pKSB7CiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbCguYmlvY19wYWNrYWdlc1shLmluc3RdLCBhc2sgPSBGKQogICAgfQp9CgouaW5zdCA8LSAuY3Jhbl9wYWNrYWdlcyAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpCmlmKGFueSghLmluc3QpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcyguY3Jhbl9wYWNrYWdlc1shLmluc3RdKQp9CgouaW5zdCA8LSAuZ2l0aHViX3BhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkKCiMgY2FyZWZ1bCBub3csIGJlY2F1c2UgdGhlIGluc3RhbGxhdGlvbiBvZiBOZXRDb01pIGNhbiBiZSBidWdneQojIGl0IHVzdWFsbHkgZmFpbHMgYmVjYXVzZSBpZiBmYWlscyB0byBpbnN0YWxsIG9uZSBvZiB0aGUgbWlzc2luZyBkZXBlbmRlbmNpZXMsCiMgaWYgYW55LiBJZiB0aGlzIGhhcHBlbnMsIGluc3RhbGwgdGhlIGRlcGVuZGVuY2llcyBmaXJzdC4uLgoKaWYoYW55KCEuaW5zdCkpewogIHJlcXVpcmUoZGV2dG9vbHMpCiAgaWYoIS5pbnN0WzFdKSBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInpkazEyMy9TcGllY0Vhc2kiKQogIGlmKCEuaW5zdFsyXSkgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJzdGVmcGVzY2hlbC9OZXRDb01pIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwZW5kZW5jaWVzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBvcyA9IGMoImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZy8iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCaW9jTWFuYWdlcjo6cmVwb3NpdG9yaWVzKCkpKQp9CgojIExvYWQgcGFja2FnZXMgaW50byBzZXNzaW9uLCBhbmQgcHJpbnQgcGFja2FnZSB2ZXJzaW9uCnNhcHBseShjKC5jcmFuX3BhY2thZ2VzLCAuYmlvY19wYWNrYWdlcywgLmdpdGh1Yl9wYWNrYWdlcyksIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKCiMgb3RoZXIgc2V0dXAgb3BlcmF0aW9ucwpvcGFyIDwtIHBhcihuby5yZWFkb25seT1UUlVFKSAKcGFyKGFzaz1GKSAKIyBzZXQuc2VlZCgxMjM0KSAKIyBwbGF5IGF1ZGlvIG5vdGlmaWNhdGlvbnMKcGxheV9hdWRpbyA8LSBUCiMgdGltZSBpbXBvcnRhbnQgc3RlcHMKa2VlcF90aW1lIDwtIFQKIyB2ZXJib3NlIG91dHB1dDogd2lsbCBwcmludCBhZGRpdGlvbmFsIG9iamVjdHMgYW5kIG1lc3NhZ2VzCnZlcmJvc2Vfb3V0cHV0IDwtIFQKCiMgbG9hZCB0aGUgZnVuY3Rpb25zIGFuZCByZXR1cm4gYSByZXBvcnQKc291cmNlKGZpbGUucGF0aCgic291cmNlIiwiTUFOX2Z1bmN0aW9ucy5SIikpCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KSAjIG5vdGlmaWNhdGlvbgpgYGAKCiMgSW50cm9kdWN0aW9uLiAgCgpUaGlzIG5vdGVib29rIHJlcG9ydHMgb24gdGhlIGluZmVyZW5jZSBvZiBtaWNyb2JpYWwgYXNzb2NpYXRpb24gbmV0d29ya3MgZnJvbSBtZXRhLXRheG9ub21pYyBkYXRhIGV4dHJhY3RlZCBmcm9tIFtEYWlyeUZNQk5dKGh0dHA6Ly9keC5kb2kub3JnLzEwLjE3NjMyLzNjd2Y3MjlwMzQuMykgdXNpbmcgcGFja2FnZSBbTmV0Q29NaV0oaHR0cHM6Ly9naXRodWIuY29tL3N0ZWZwZXNjaGVsL05ldENvTWkpLCBhZnRlciBzb21lIHByZS1wcm9jZXNzaW5nIGNhcnJpZWQgb3V0IG1vc3RseSB3aXRoIHBhY2thZ2UgW3BoeWxvc2VxXShodHRwczovL2pvZXk3MTEuZ2l0aHViLmlvL3BoeWxvc2VxLykuICAKClRoZSBvYmplY3RpdmUgb2YgdGhpcyBhbmFseXNpcyBpczogIAoKKiBpbmZlciBtaWNyb2JpYWwgYXNzb2NpYXRpb24gbmV0d29ya3MgKGF0IHRoZSBnZW51cyBsZXZlbCkgZm9yIHNlbGVjdGVkIGNoZWVzZSBzdHVkaWVzIHN0b3JlZCBpbiBEYWlyeUZNQk4gdXNpZ24gZGlmZmVyZW50IGluZmVyZW5jZSBtZXRob2RzICAKCiogaWRlbnRpZnkgYXNzb2NpYXRpb25zIHdoaWNoIGFyZSBjb25zZXJ2ZWQgYWNyb3NzIG1ldGhvZHMgIAoKKiBjb21wYXJlIGRpZmZlcmVudCBpbmZlcmVuY2UgbWV0aG9kcyAKCiogaWRlbnRpZnkgYW5kIGNvbXBhcmUgaHVicyBpbiBkaWZmZXJlbnQgbmV0d29ya3MKICAKVGhlIGRhdGEgdXNlZCBpbiB0aGlzIHNjcmlwdCBpbmNsdWRlIHN0dWRpZXMgZnJvbSBGTUJOIHZlcnNpb25zIDMuMiwgd2l0aCBkaWZmZXJlbnQgcGxhdGZvcm1zIGFuZCB0YXJnZXQsIGJ1dCBhbmFseXplZCB1c2luZyB0aGUgc2FtZSBwaXBlbGluZSBiYXNlZCBvbiBEQURBMi4gVGhlIHNjcmlwdCBpcyBkZXNpZ25lZCB0byB3b3JrIHdpdGggcGh5bG9zZXEgb2JqZWN0cyBleHRyYWN0ZWQgZnJvbSBGb29kTWljcm9iaW9uZXQgb3IgRGFpcnlGTUJOIHVzaW5nIHRoZSBTaGlueUZNQk4gYXBwIERhaXJ5Rk1CTiBjYW4gYmUgZG93bmxvYWRlZCBmcm9tIFtNZW5kZWxleSBkYXRhXShodHRwczovL2RhdGEubWVuZGVsZXkuY29tL2RhdGFzZXRzLzNjd2Y3MjlwMzQvMyk6IG5ldyB2ZXJzaW9ucyBhcmUgZHVlIHNvb24uICAKVGhlIHNjcmlwdCB3aWxsIGFsc28gd29yayBjb3JyZWN0bHkgd2l0aCBwaHlsb3NlcSBvYmplY3RzIGdlbmVyYXRlZCB1c2luZyB0aGUgREFEQTIgW0Jpb0NvbmR1Y3RvciBwaXBlbGluZV0oaHR0cHM6Ly9iZW5qam5lYi5naXRodWIuaW8vZGFkYTIvdHV0b3JpYWwuaHRtbCkgYnV0IHRoZSB1c2Ugb2YgU0lMVkEgYXMgYSB0YXhvbm9taWMgZGF0YWJhc2UgaXMgYWR2aXNhYmxlLiAgV2l0aCBzb21lIGFkYXB0YXRpb25zIHRoZSBzY3JpcHQgbWF5IHdvcmsgd2l0aCBwaHlsb3NlcSBvYmplY3RzIGdlbmVyYXRlZCB1c2luZyBvdGhlciBwaXBlbGluZXMuICAKCiMgQW5hbHlzaXMgd29ya2Zsb3cuICAKCjEuICoqc3RhcnQgZnJvbSBuIChuPjEpIGBwaHlsb3NlcWAgb2JqZWN0cyoqLiBJbiB0aGlzIGFuYWx5c2lzIEkgd2lsbCB1c2UgcGh5bG9zZXEgb2JqZWN0cyBleHRyYWN0ZWQgZnJvbSBEYWlyeUZNQk4gKHdoaWNoLCBpbiB0dXJuLCBpbmNsdWRlcyBhbGwgc3R1ZGllcyBvbiBkYWlyeSBwcm9kdWN0cyBzdG9yZWQgaW4gRm9vZE1pY3JvYmlvbmV0KS4gQWx0aG91Z2ggcG9vbGluZyBhdCB0aGUgZ2VudXMgbGV2ZWwgaXMgbGlrZWx5IHRvIG11ZGRsZSBzb21lIGludGVyYWN0aW9ucywgaXQgaXQgdGhlIG9ubHkgcmVhc29uYWJsZSBjaG9pY2Ugd2hlbiBjb21wYXJpbmcgc3R1ZGllcyB3aXRoIHZhc3RseSBkaWZmZXJlbnQgdGFyZ2V0cywgc2VxdWVuY2luZyBwbGF0Zm9ybXMsIGJpb2luZm9ybWF0aWMgcGlsZWxpbmVzLCBldGMuIAoKMi4gKipwZXJmb3JtIGRhdGEtd3JhbmdsaW5nIHdpdGggcGh5bG9zZXEgZnVuY3Rpb25zIChtb3N0bHkpKiouIFRoZXJlIGFyZSBhIG51bWJlciBvZiBiYXNpYyBvcGVyYXRpb25zIHdoaWNoIHNob3VsZCBiZSBwZXJmb3JtZWQ6ICAKCiAgICArIHJlbW92ZSBzYW1wbGVzIHdpdGggYSBsb3cgbnVtYmVyIG9mIHNlcXVlbmNlcyAoYG1pbnNlcWApIHVzaW5nIHRoZSBwcnVuZV9zYW1wbGVzKCkgZnVuY3Rpb25zIChhbmQgcGVyaGFwcyBza2lwIHRoZSBhbmFseXNpcyBpZiB0aGVyZSBpcyBsZXNzIHRoYW4gbWluX3NhbXBsZXMpLiBOb3RlIHRoYXQgc2FtcGxlIGFuZCB0YXhvbm9taWMgZmlsdGVyaW5nIGNhbiBiZSBhbHNvIHBlcmZvcm1lZCB3aXRoaW4gdGhlIGBOZXRDb01pOjpuZXRDb25zdHJ1Y3QoKWAgZnVuY3Rpb24KICAKICAgICsgcmVtb3ZlIF9FdWthcnlvdGFfIGFuZCBfQ2hsb3JvcGxhc3RzXyB1c2luZyBzdWJzZXRfdGF4YSgpCiAgCiAgICArIHJlbW92ZSB0YXhhIHdpdGggYW1iaWd1b3VzIGlkZW50aWZpY2F0aW9uIG9yIHBvb3IgaWRlbnRpZmljYXRpb24gKHNheSBhdCB0aGUgZG9tYWluIG9yIHBoeWx1bSBsZXZlbCkKICAKICAgICsgcGVyZm9ybSBwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UgZmlsdGVyaW5nIChwcmV2YWxlbmNlIGFib3ZlIG1pbl9wcmV2IEFORCBhYnVuZGFuY2UgYWJvdmUgbWluX3JlbF9hYiksIHBvc3NpYmx5IHVzaW5nIGBmaWx0ZXJfdGF4YSgpYAogIAogICAgKyByZXR1cm4gc29tZSBzb3J0IG9mIHN1bW1hcnkgZm9yIHdoYXQgaGFzIGJlZW4gZG9uZSBhbmQgdGhlIGVmZmVjdCBvbiB0aGUgb3JpZ2luYWwgdGFibGUgKGluIHRlcm1zIG9mIGxvc3Mgb2YgdGF4YSwgc2VxdWVuY2VzLCBzYW1wbGVzKSwgaW5jbHVkaW5nIHNvbWUgbWVhc3VyZSBvZiBkaXZlcnNpdHkgYW5kIGV2ZW5uZXNzICAKICAKMy4gKipwZXJmb3JtIG1pY3JvYmlhbCBhc3NvY2lhdGlvbiBuZXR3b3JrIGluZmVyZW5jZSB3aXRoIGBuZXRDb25zdHJ1Y3QoKWAqKiB3aXRoIHNvbWUgZXJyb3IgdHJhcHBpbmcgYW5kIHN0b3JlIHJlc3VsdHMgaW4gYSBsaXN0LiBUaGlzIGV4YW1wbGUgdXNlcyB0aGUgZm9sbG93aW5nIGluZmVyZW5jZSBtZXRob2RzOiAgCgogICAgKyAqKlNwYXJDQyoqIChzcGFyY2MoKSBpbiBTcGllY0Vhc2kgcGFja2FnZSk6IG5lZWRzIGhpZ2ggbnVtYmVyIG9mIHRheGEgYW5kIHNwYXJzZSBtYXRyaXggIAogICAgCiAgICArICoqQ0NSRVBFKiogKGNjcmVwZSBwYWNrYWdlLCBpdCBpcyB0aGUgbWFpbiBhcHByb2FjaCB1c2VkIGluIENvTmV0KSAgCiAgICAKICAgICsgKipTcGllY0Vhc2kqKiAoU3BpZWNFYXNpIHBhY2thZ2UpICAKICAgIAogICAgKyAqKlNQUklORyoqIChTUFJJTkcgcGFja2FnZSkgIAoKNC4gKipnZXQgbm9kZXMsIGVkZ2VzIGFuZCBuZXR3b3JrIHN0YXRzKio6IHVzZSBOZXRDb01pOjpuZXRBbmFseXplKCkgdG8gZ2V0IGFuIG9iamVjdCB3aXRoIGJvdGggbm9kZSBhbmQgbmV0d29yayBzdGF0czsgcHJpbnQgYSBzdW1tYXJ5IGZvciBuZXR3b3JrIHN0YXRzOyBidWlsZCBhIHRpZHlncmFwaCBvYmplY3QgZm9yIGZ1cnRoZXIgc3RhdHM7IGJ1aWxkIHRpZHkgZGF0YSBmcmFtZXMgd2l0aCByZWxldmFudCBzdGF0czsgCgpUaGUgc2NyaXB0IGlzIGVzaWduZWQgdG8gc2F2ZSBkYXRhIGFuZCBhbW55IG9mIHRoZSBwbG90cyBpbiBhIHN1YmZvbGRlciAoaGVyZSAibmV0Y29tcGFyZV9vdXRwdXQiKS4gIAoKIyBUaGUgYW5hbHlzaXMuCgojIyBTZXR0aW5nIHRoZSBvcHRpb25zLiAgCgpBIG51bWJlciBvZiBvcHRpb25zIHBlcnRhaW5pbmcgdG8gdGhlIGFuYWx5c2lzIG9yIHJsYXRlZCB0byBzYXZlIG9wZXJhdGlvbnMgY2FuIGJlIHNldCBpbiB0aGUgY2h1bmsgYmVsb3cgYW5kIHdpbGwgYmUgc2F2ZWQgYW5kIHByaW50ZWQuIAogIApgYGB7ciBzZXR0aW5nX29wdGlvbnMsIGVjaG89RkFMU0V9CgojIyMgcHJvY2Vzc2luZyBhbmQgZm9sZGVycyAjIyMKIyB0aGUgZm9sbG93aW5nIGNvbW1hbmQgZGV0ZWN0cyB0aGUgbnVtYmVyIG9mIGNvcmVzIG9uIFVOSVgvTWFjT1MKbmNvcmVzIDwtIHBhcmFsbGVsOjpkZXRlY3RDb3Jlcyhsb2dpY2FsID0gRikgIyB0byBkZXRlY3QgcGh5c2ljYWwgY29yZXMgaW4gTWFjT1MKIyB0aGUgbmFtZSBvZiB0aGUgZm9sZGVyIGNvbnRhaW5pbmcgdGhlIHBoeWxvc2VxIG9iamVjdHMKZGF0YV9mb2xkZXIgPC0gImlucHV0X2RhdGEiCiMgdGhlIG5hbWUgb2YgdGhlIGZvbGRlciBjb250YWluaW5nIGFkZGl0aW9uYWwgZnVuY3Rpb25zICsgb3B0aW9uYWwgZGF0YSAobGlrZSB0aGUgbG9va3VwIHRhYmxlKQpzb3VyY2VfZm9sZGVyIDwtICJzb3VyY2UiCiMgdGhlIG5hbWUgb2YgdGhlIG91dHB1dCBmb2xkZXIKb3V0cHV0X2ZvbGRlciA8LSAibmV0Y29tcGFyZV9vdXRwdXQiCnByb2Nlc3NfYmF0Y2ggPC0gVAojIGlmIHRydWUsIGFsbCBwaHlsb3NlcXMgaW4gdGhlIHN1YmZvbGRlciBwaHlzZXFvYmogd2lsbCBiZSBwcm9jZXNzZWQgaW4gYmF0Y2ggKG1heSB0YWtlIHRpbWUpCgojIHNvbWUgZ3JhcGggb3B0aW9ucywgc2hhcmVkIGV2ZXJ5d2hlcmUKZHBpX29wdGlvbiA8LSAzMDAKZ190eXBlIDwtICJ0aWZmIiAKCiMgdXNlIGEgbG9va3VwIHRhYmxlIHRvIGZpeCB0aGUgZ2VuZXJhIGFuZCBmYW1pbGllcyBmb3IgTGFjdG9iYWNpbGxhbGVzCnVzZV9sb29rdXAgPC0gVAppZih1c2VfbG9va3VwKXsKICBsb29rdXBfdGFibGUgPC0gcmVhZF90c3YoZmlsZS5wYXRoKCJzb3VyY2UiLCAibG9va3VwX2xhY3RvYmFjaWxsdXMudHh0IiksIGNvbF90eXBlcyA9ICJjY2NjY2MiKQp9CgpvdXRfZmlsZW5hbWVfcHJlZiA8LSAiTUFOX0ZNQk5fZ2VudXNfMyIgIyB0aGUgcHJlZml4IHdoaWNoIHdpbGwgYmUgdXNlZCBmb3IgYWxsIG91dHB1dCBmaWxlbmFtZXMKCiMjIyBmaWx0ZXJpbmcgb3B0aW9ucyAjIyMKbWluX3NhbXBsZXMgPC0gMjAKIyB0aGUgbWluaW11bSBudW1iZXIgb2Ygc2FtcGxlcyBpbiB0aGUgZGF0YSBzZXQ6IHN0dWRpZXMgd2l0aCA8IG1pbl9zYW1wbGVzIGFmdGVyIGZpbHRlcmluZyB3aWxsIGJlIGV4Y2x1ZGVkIGZyb20gdGhlIGFuYWx5c2lzCm1pbl9zZXFzIDwtIDEwMDAKIyB0aGUgbWluaW11bSBudW1iZXIgb2Ygc2VxdWVuY2VzIHBlciBzYW1wbGUKCiMgcmVtb3ZlcyB1bmNoYXJhY3Rlcml6ZWQgdGF4YSAoaS5lLiB0YXhhIG1pc3NpbmcgdGhlIGlkZW50aWZpY2F0aW9uIGF0IHRoZSBwaHlsdW0gbGV2ZWwgb3IgYWJvdmUpCnJtX3VuY2hhciA8LSBUCiMgcmVtb3ZlIEV1a2FyeW90YQpybV9ldWsgPC0gVAojIHJlbW92ZSBjaGxvcm9waGFsc3RzIGFuZCBtaXRvY2hvbmRyaWEKcm1fY2hsbWl0IDwtIFQKIyB0YXhvbm9taWMgYWdnbG9tZXJhdGlvbiBvcHRpb25zOiAibm9uZSIgbWVhbnMgbm8gYWdncmVnYXRpb247IGlmIHRoZSBpbnB1dCBkYXRhIGFyZSBBU1ZzIG9yIE9UVXMgdGhleSB3aWxsIGJlIHVzZWQgYXMgc3VjaAojIGlmIHRoZSBpbnB1dCBmaWxlIGlzIGZyb20gRk1CTiBkYXRhIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0cyBhcmUgYWdncmVnYXRlZCBhdCBkaWZmZXJlbnQgdGF4b25vbWljIGxldmVscy4KIyB3aGVuIGNvbXBhcmluZyBkYXRhc2V0cyBvYnRhaW5lZCB1c2luZyBkaWZmZXJlbnQgZ2VuZSB0YXJnZXRzIG9yIHBpcGVsaW5lcyBzaG91bGQgYmUgc2V0IHRvICJnZW51cyIKIyAibm9uZSIsICJnZW51cyIsIG9yICJmYW1pbHkiKTsgZG8gbm90IHVzZSBzcGVjaWVzLCB3aWxsIG1vc3QgbGlrZWx5IGNhdXNlIGVycm9ycwp0YXhnbG9tIDwtICJnZW51cyIgIyB0YXggZ2xvbSB3aWxsIGJlIHBlcmZvcm1lZCBhdCB0aGUgZ2VudXMgbGV2ZWwKIyByZW1vdmUgdGF4YSB3aGljaCBhcmUgdW5jaGFyYWN0ZXJpemVkIGF0IHRoZSBmYW1pbHksIG9yZGVyIGFuZCBjbGFzcyBsZXZlbCAoc2hvdWxkIG5vdCBiZSB2ZXJ5IG1hbnkgaW4gbW9zdCBkYXRhc2V0cykKYWJvdmVfZ2VudXNfZmxhZyA8LSBUCiMgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcgojIGZsYWcgdG8gZGVjaWRlIGlmIE9UVSBzaG91bGQgYmUgZmlsdGVyZWQsIGkuZS4gaWYgdGhlIGZpbHRlciBpcyB0byBiZSBhcHBsaWVkIGF0IGFsbApmaWx0ZXJPVFVzIDwtIFRSVUUKIyBmbGFnIHRvIHNldCB0aGUgYXBwbGljYXRpb24gb2YgYSBwcmV2YWxlbmNlIGZpbHRlcgpwcmV2X2ZpbHRlciA8LSBUUlVFCiMgdGhlIHRocmVzaG9sZCBvZiB0aGUgZmlsdGVyLCBhcyBmcmFjdGlvbiBvZiBzYW1wbGVzCnByZXZfdGhyZXNob2xkIDwtIDAuMDUKIyB0aGUgYWJ1bmRhbmNlIGFzIGZyYWN0aW9uIG9mIHNlcXVlbmNlcyAoaW4gYXQgbGVhc3Qgb25lIHNhbXBsZSkKYWJfdGhyZXNob2xkIDwtIDAuMDA1CnBhc3NfYm90aCA8LSBUUlVFIAojIGlmIHRydWUgYW4gT1RVIG11c3QgcGFzcyBib3RoIGFidW5kYW5jZSBhbmQgcHJldmFsZW5jZSBmaWx0ZXIsIG90aGVyd2lzZQojIHBhc3NpbmcgZWl0aGVyIGlzIGVub3VnaAojIG9wdGlvbnMgZm9yIHNhdmluZyBvdXRwdXQgb2YgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcgpzYXZlX3ByZXZfYWJfcGxvdCA8LSBUCiMgZmxhZyBmb3Igc2F2aW5nIHRoZSBwcmV2IGFiIHBsb3QKcHJpbnRfcHJldl9hYl9wbG90IDwtIEYKIyBmbGFnIGZvciBwcmludGluZyB0aGUgcGxvdApzYXZlX3ByZXZfdGFibGUgPC1UIAojIGZsYWcgZm9yIHNhdmluZyB0aGUgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIHRhYmxlOyBzaG91bGQgYmUgc2F2ZWQgYmVjYXVzZSBpdCBjYW4gYmUgdXNlZCBsYXRlcgojIHRvIGFkZCBpbmZvIHRvIHRoZSBub2RlIHN0YXRzCnNhdmVfcHJldl9hYl9saXN0IDwtVAoKIyBhIHZlY3RvciB3aXRoIHRoZSBtZXRob2RzIHVzZWQgZm9yIGluZmVyZW5jZSwgdXNlIHRoZSBzcGVsbGluZyB1c2VkIGluIGBuZXRDb25zdHJ1Y3QoKWAKIyBzZWVOZXRDb01pIGRvY3VtZW50YXRpb24gZm9yIGEgbHNpdCBvZiBhdmFpbGFibGUgbWV0aG9kcyBhbmQgb3B0aW9uczsKIyB0aGUgZmlyc3QgbWV0aG9kIGlzIHRoZSBvbmUgd2hpY2ggd2lsbCBiZSB1c2VkIHRvIGRvIGNvbXBhcmlzb25zIHdpdGhpbiBzYW1wbGUKIyBpZiB5b3Ugd2FudCB0byBydW4ganVzdCBvbmUgbGlzdCBvbmx5IHRoYXQKCmluZl9tZXRob2RzIDwtIGMoInNwaWVjZWFzaSIsICJzcHJpbmciLCAic3BhcmNjIiwgImNjcmVwZSIpCgojIHRoZSBsaXN0IG9mIHBhcmFtZXRlcnMgdG8gYmUgcGFzc2VkIGluIGBuZXRDb25zdHJ1Y3QoKWAgZm9yIGVhY2ggb2YgdGhlIG1ldGhvZHM7CiMgdGhlcmUgbXVzdCBiZSBvbmUgZW50cnkgZm9yIGVhY2ggb2YgaGUgcGFyYW1ldGVycyBpbiB0aGUgbGlzdAojIHNvbWUgcGFyYW1ldGVycyBhcmUgbm90IHJlYWxseSBuZWVkZWQgYW5kIHdpbGwgYmUgaWdub3JlZAppbmZfbWV0aG9kc19wYXJhbSA8LSBsaXN0KAogIHNwaWVhY2Vhc2kgPSBsaXN0KAogICAgc3Bhck1ldGhvZCA9ICJ0LXRlc3QiLAogICAgYWxwaGEgPSAwLjAwMSwKICAgIG1lYXN1cmVQYXJMaXN0ID0gbGlzdCgKICAgICAgbWV0aG9kID0gJ21iJywKICAgICAgbGFtYmRhLm1pbi5yYXRpbyA9IDFlLTIsCiAgICAgIG5sYW1iZGEgPSAyMCwKICAgICAgcHVsc2FyLnBhcmFtcyA9IGxpc3QocmVwLm51bSA9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgNTApCiAgICApLAogICAgbm9ybW1ldGhvZFBhciA9ICJub25lIiwKICAgIHplcm9tZXRob2RQYXIgPSAibm9uZSIsCiAgICBkaXNzRnVuY1BhciA9ICJzaWduZWQiLAogICAgdmVyYm9zZVBhciA9IDEKICApLAogIHNwcmluZyA9IGxpc3QoCiAgICBzcGFyTWV0aG9kID0gInQtdGVzdCIsCiAgICBhbHBoYSA9IDAuMDAxLAogICAgbWVhc3VyZVBhckxpc3QgPSBsaXN0KG5sYW1iZGEgPSA1MCwgcmVwLm51bSA9IDUwKSwKICAgICAgICBub3JtbWV0aG9kUGFyID0gIm5vbmUiLAogICAgemVyb21ldGhvZFBhciA9ICJub25lIiwKICAgIGRpc3NGdW5jUGFyID0gInNpZ25lZCIsCiAgICB2ZXJib3NlUGFyID0gMQogICksCiAgc3BhcmNjID0gbGlzdCgKICAgIHNwYXJNZXRob2QgPSAidC10ZXN0IiwKICAgIGFscGhhID0gMC4wMDEsCiAgICBtZWFzdXJlUGFyTGlzdCA9IGxpc3QoaXRlciA9IDEwMCwgaW5uZXJfaXQgPSAyMCwgdGggPSAwLjA1KSwKICAgIG5vcm1tZXRob2RQYXIgPSAibm9uZSIsCiAgICB6ZXJvbWV0aG9kUGFyID0gIm5vbmUiLAogICAgZGlzc0Z1bmNQYXIgPSAic2lnbmVkIiwKICAgIHZlcmJvc2VQYXIgPSAxCiAgKSwKICBjY3JlcGUgPSBsaXN0KAogICAgc3Bhck1ldGhvZCA9ICJ0LXRlc3QiLAogICAgYWxwaGEgPSAwLjAwMSwKICAgIG1lYXN1cmVQYXJMaXN0ID0gTlVMTCwKICAgIG5vcm1tZXRob2RQYXIgPSAiZnJhY3Rpb25zIiwKICAgIHplcm9tZXRob2RQYXIgPSAibm9uZSIsCiAgICBkaXNzRnVuY1BhciA9ICJzaWduZWQiLAogICAgdmVyYm9zZVBhciA9IDEKICApCikKCiMgb3B0aW9ucyBmb3IgbmV0d29yayBhbmFseXNpcwojIGRvIFZlbm4gcGxvdApkb19WZW5uIDwtIFQKIyBjYWxjdWxhdGVzIGVkZ2UgYmV0d2Vlbm5lc3MKY2FsY19lX2JldHcgPC1UCiMgbWVyZ2Ugbm9kZSBzdGF0cwptZXJnZV9ub2RlX3N0YXRzIDwtIFQKCiMgYnVpbGQgYW5kIHNhdmUgYSBsaXN0IHdpdGggdGhlIG9wdGlvbnMsIGZvciByZXByb2R1Y2liaWxpdHkKb3B0aW9uX2xpc3QgPC0gbGlzdCgKICBnZW5lcmFsX29wdGlvbnMgPSBsaXN0KAogICAgbmNvcmVzID0gbmNvcmVzLAogICAgZGF0YV9mb2xkZXIgPSBkYXRhX2ZvbGRlciwKICAgIHNvdXJjZV9mb2xkZXIgPSBzb3VyY2VfZm9sZGVyLAogICAgb3V0cHV0X2ZvbGRlciA9IG91dHB1dF9mb2xkZXIsCiAgICBwcm9jZXNzX2JhdGNoID0gcHJvY2Vzc19iYXRjaCwKICAgIHVzZV9sb29rdXAgPSB1c2VfbG9va3VwLAogICAgb3V0X2ZpbGVuYW1lcyA9IG91dF9maWxlbmFtZV9wcmVmLAogICAgZ3JlcyA9IGRwaV9vcHRpb24sCiAgICBndHlwZSA9IGdfdHlwZQogICksCiAgbWV0aG9kc19vcHRpb25zID0gbGlzdCgKICAgIG1ldGhvZHMgPSBpbmZfbWV0aG9kcywKICAgIHBhcmFtZXRlcnMgPSBpbmZfbWV0aG9kc19wYXJhbQogICksCiAgZmlsdGVyaW5nX29wdGlvbnMgPSBsaXN0KAogICAgbWluX3NhbXBsZXMgPSBtaW5fc2FtcGxlcywKICAgIG1pbl9zZXFzID0gbWluX3NlcXMsCiAgICBnbG9tX3RheGEgPSB0YXhnbG9tLAogICAgcm11bmNoYXJfZHAgPSBybV91bmNoYXIsCiAgICBybWNobG1pdCA9IHJtX2NobG1pdCwKICAgIHJtZXVrID0gcm1fZXVrLAogICAgcm11bmNoYXJfY29mID0gYWJvdmVfZ2VudXNfZmxhZywKICAgIGZpbHRlcl9PVFVzID0gZmlsdGVyT1RVcywKICAgIHByZXZmaWx0ZXIgPSBwcmV2X2ZpbHRlciwKICAgIHByZXZ0aHJlc2hvbGQgPSBwcmV2X3RocmVzaG9sZCwKICAgIGFidGhyZXNob2xzID0gYWJfdGhyZXNob2xkLAogICAgcGFzc2JvdGggPSBwYXNzX2JvdGgsCiAgICBzYXZlcHJldmFicGxvdCA9IHNhdmVfcHJldl9hYl9wbG90LCAKICAgIHByaW50cHJldmFicGxvdCA9IHByaW50X3ByZXZfYWJfcGxvdCwKICAgIHNhdmVwcmV2YWJ0YWJsZSA9IHNhdmVfcHJldl90YWJsZSwKICAgIHNhdmVfcHJldl9hYl9saXN0ID0gc2F2ZV9wcmV2X2FiX2xpc3QKICApLAogIG5ldHN0YXRfb3B0aW9ucyA9IGxpc3QoCiAgICBkb1Zlbm4gPSBkb19WZW5uLAogICAgY2FsY0ViZXR3ID0gY2FsY19lX2JldHcsCiAgICBtZXJnZU5zdGF0cyA9IG1lcmdlX25vZGVfc3RhdHMKICApCikKCnNhdmUob3B0aW9uX2xpc3QsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0X2ZvbGRlciwgcGFzdGUob3V0X2ZpbGVuYW1lX3ByZWYsIl9vcHRpb25zLlJkYXRhIikpKQoKCmV4dF9mdW5jdGlvbnMgPC0gcHJpbnRfZnVuY3Rpb25fcmVwb3J0KCkKaWYodmVyYm9zZV9vdXRwdXQpIHsKICBwcmludChleHRfZnVuY3Rpb25zKQogIHByaW50KG9wdGlvbl9saXN0KQogIH0KCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KSAjIG5vdGlmaWNhdGlvbgoKCmBgYAojIyBMb2FkaW5nIHRoZSBvYmplY3RzIHRvIHByb2Nlc3MuICAKClRoZSBpbnB1dCBkYXRhIGFyZSBzdG9yZWQgaW4gYSBmb2xkZXIgKGlucHV0X2RhdGEpIGFuZCBwcm9jZXNzZWQgb25lIGJ5IG9uZSAoZGVwZW5kaW5nIG9uIHVzZXIgaW5wdXQpIG9yIGluIGJhdGNoIChzZWUgcHJvY2Vzc19iYXRjaCBvcHRpb24pLiBUaGUgZm9sZGVyIHNob3VsZCBhbHNvIGNvbnRhaW4gYSAudHN2IHdpdGggc3R1ZHkgbWV0YWRhdGEsIHdpdGggYSBsYWJlbCB2YXJpYWJsZSB3aXRoIHRoZSBudW1iZXIgb2YgdGhlIHN0dWR5IGFuZCBhbiBpbmRpY2F0b3Igb24gdGhlIG5hdHVyZSBvZiB0aGUgZmlsZSAoRk1CTiBvciwgaWYgdGhlIGFjY24gbnVtYmVyIGlzIGluIHRoZSBuYW1lLCBBU1YpLiBIZXJlIEkgYW0gbG9hZGluZyA1IHN0dWRpZXMgZnJvbSBEYWlyeUZNQk4gKFNUMTA2LCBTVDExMCwgU1QxMTUsIFNUMTMxLCBTVDEzNikuIFRoZSBzdHVkaWVzIGRpZmZlciBpbiB0aGUgdGFyZ2V0cyAoU1QxMDYgdGFyZ2V0cyBWMS1WMyBSTkEsIHRoZSBvdGhlcnMgdGhlIDE2UyBSTkEgZ2VuZSwgVjQgb3IgVjMtVjQpIGFuZCBwbGF0Zm9ybS4gTG9vayBhdCB0aGUgZXhhbXBsZSBmaWxlcyB0byBzZWUgaG93IHRoZSBzdHVkeV9tZXRhZGF0YSBmaWxlIHNob3VsZCBiZSBzZXR1cC4gICAKCmBgYHtyIGxvYWRfaW5wdXRfZGF0YSwgZWNobyA9IEZBTFNFfQoKaWYoa2VlcF90aW1lKSB0aWMoImxvYWRpbmcgcGh5bG9zZXEgb2JqZWN0cyIpCiMgb2J0YWluIHRoZSBsaXN0IG9mIGZpbGVzIGluIHRoZSBpbnB1dCBmb2xkZXIKaW5wdXRfZmlsZV9saXN0IDwtIGxpc3QuZmlsZXMoZmlsZS5wYXRoKGRhdGFfZm9sZGVyKSkKIyBtYW5hZ2UgLnJkYXRhIGZpbGVzIGZpcnN0IChidXQgdGhleSBtdXN0IGJlIHBoeWxvc2VxIG9iamVjdHMgb3Igd2lsbCBiZSBkZWxldGVkIGxhdGVyKQpyZGF0YV9pbnB1dF9maWxlX2xpc3QgPC0gaW5wdXRfZmlsZV9saXN0W3N0cl9kZXRlY3QoaW5wdXRfZmlsZV9saXN0LCAiLnJkYXRhfC5SZGF0YXwuUkRBVEEiKV0gCiMgZ2V0IHRoZSBsaXN0IC5SZGF0YQpyZGF0YV9uYW1lc19saXN0IDwtIHN0cl9yZW1vdmUocmRhdGFfaW5wdXRfZmlsZV9saXN0LCAiLnJkYXRhfC5SZGF0YXwuUkRBVEEiKQpyZGF0YV9wYXRoX2xpc3QgPC0gZmlsZS5wYXRoKGRhdGFfZm9sZGVyLHJkYXRhX2lucHV0X2ZpbGVfbGlzdCkKIyBsb2FkIGFuZCBzYXZlIGFzIC5yZHMsIHJlbW92ZSBvYmplY3RzIGFuZCBmaWxlcwpmb3IgKGkgaW4gc2VxX2Fsb25nKHJkYXRhX3BhdGhfbGlzdCkpewogIGxvYWQocmRhdGFfcGF0aF9saXN0W2ldKSAjIGxvYWRzIHRoZSBvYmplY3QgYXMgcGh5c2VxZGF0YQogIHNhdmVSRFMocGh5c2VxZGF0YSwgZmlsZS5wYXRoKGRhdGFfZm9sZGVyLCBwYXN0ZShyZGF0YV9uYW1lc19saXN0W2ldLCIucmRzIixzZXA9IiIpKSkKICBybShwaHlzZXFkYXRhKQogIGZpbGUucmVtb3ZlKHJkYXRhX3BhdGhfbGlzdFtpXSkKfQoKIyBnZXQgdGhlIG5hbWVzIGZvciB0aGUgLnJkcyBmaWxlcwoKaW5wdXRfZmlsZV9saXN0IDwtIGxpc3QuZmlsZXMoZmlsZS5wYXRoKGRhdGFfZm9sZGVyKSkKcmRzX2lucHV0X2ZpbGVfbGlzdCA8LSBpbnB1dF9maWxlX2xpc3Rbc3RyX2RldGVjdChpbnB1dF9maWxlX2xpc3QsICIucmRzfC5SRFMiKV0gCm5hbWVzX2xpc3QgPC0gc3RyX3JlbW92ZShyZHNfaW5wdXRfZmlsZV9saXN0LCAiLnJkc3wuUkRTIikKcmRzX3BhdGhfbGlzdCA8LSBmaWxlLnBhdGgoZGF0YV9mb2xkZXIscmRzX2lucHV0X2ZpbGVfbGlzdCkKIyBsb2FkIGFsbCB0aGUgZmlsZXMgaW4gYSBsaXN0IHVzaW5nIGEgZnVuY3Rpb25hbApwaHlzZXFfbGlzdCA8LSBsYXBwbHkocmRzX3BhdGhfbGlzdCwgcmVhZFJEUykKIyBjaGVjayBpZiBhbGwgYXJlIHBoeWxvc2VxIG9iamVjdHMKaXNfcGh5bG9zZXEgPC0gc2FwcGx5KHBoeXNlcV9saXN0LCBmdW5jdGlvbih4KSBjbGFzcyh4KT09InBoeWxvc2VxIikKIyBjbGVhbiB1cCB0aGUgbGlzdCBpZiBuZWNlc3NhcnkgYW5kIGdpdmUgbmFtZXMKcGh5c2VxX2xpc3QgPC0gcGh5c2VxX2xpc3RbaXNfcGh5bG9zZXFdCm5hbWVzKHBoeXNlcV9saXN0KSA8LSBuYW1lc19saXN0W2lzX3BoeWxvc2VxXQojIGlmIHByb2Nlc3NfYmF0Y2ggPT0gRiBuZWVkIHRvIGluZGljYXRlIHdoaWNoIGlzIHRvIGJlIHByb2Nlc3NlZDsgZGVmYXVsdHMgdG8gdGhlIGZpcnN0IGVsZW1lbnQgb2YgdGhlIGxpc3QKIyBzbywgaXQgaXMgZWFzaWVyIGlmIHlvdSBqdXN0IGhhdmUgb25lIGVsZW1lbnQgaW4gdGhlIGZvbGRlcgppdGVtX3RvX3Byb2Nlc3MgPC0gaXRlbSA8LSBOQV9pbnRlZ2VyXwppZighcHJvY2Vzc19iYXRjaCkge2l0ZW0gPC0gaWZlbHNlKGlzLm5hKGl0ZW1fdG9fcHJvY2VzcyksIDEsIGl0ZW1fdG9fcHJvY2Vzcyl9CmlmKCFpcy5uYShpdGVtKSl7CiAgcGh5c2VxX2xpc3QgPC0gcGh5c2VxX2xpc3RbaXRlbV0KICB9CgoKIyBzaG91bGQgY2hlY2sgaWYgdGhpcyBpcyB0b28gbGFyZ2UKIyBzaXplX2xpbWl0IApzaXplX2xpbWl0IDwtIDUwMGU2TApwaHlzZXFfc2l6ZSA8LSBvYmpfc2l6ZShwaHlzZXFfbGlzdCkKc2l6ZV93YXJuaW5nIDwtIGlmZWxzZShhcy5udW1lcmljKHBoeXNlcV9zaXplKT5zaXplX2xpbWl0LAogICAgICAgICAgICAgICAgICAgICAgICJXQVJOSU5HOiB0b28gbXVjaCBkYXRhIHRvIHByb2Nlc3MiLAogICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJvYmplY3Qgc2l6ZSIsIGFzLmNoYXJhY3RlcihwaHlzZXFfc2l6ZSksICIgQiIsIHNlcCA9ICIgIikpIAojIGxvYWQgdGhlIHN0dWR5IG1ldGFkYXRhIChpZiBub3QgYXZhaWxhYmxlIG9yIG5vdCBwcm9wZXJseSBmb3JtYXR0ZWQgdGhlIHNjcmlwdCB3aWxsIGNyZWF0ZSBhIHN1aXRhYmxlIGRhdGEgZnJhbWUpCiMgbm8gaW5wdXQgcmVxdWlyZWQsIHNob3VsZCB3b3JrIHdpdGggZGVmYXVsdHMKc3R1ZHlfbWV0YWRhdGEgPC0gbG9hZF9tZXRhZGF0YSgpCiMgcHJpbnQgdGhlIHN0dWR5IG1ldGFkYXRhCnN0dWR5X21ldGFkYXRhICU+JSAKICBkcGx5cjo6c2VsZWN0KGxhYmVsOnN0dWR5SWQsIHNhbXBsZXMsIHNob3J0X2Rlc2NyLCB0eXBlLCByZWZfc2hvcnQpCgpsb2FkaW5nX3JlcG9ydCA8LSB0aWJibGUoIG9iamVjdHNfdG9fcHJvY2VzcyA9IGMobmFtZXNfbGlzdCxzaXplX3dhcm5pbmcpKQppZih2ZXJib3NlX291dHB1dCkgcHJpbnQobG9hZGluZ19yZXBvcnQpCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KQppZihrZWVwX3RpbWUpIHRvYygpCgpgYGAKCiMjIEZpbHRlcmluZy4gIAoKRmlsdGVyaW5nIG9wdGlvbmFsbHkgZmlsdGVycyBzYW1wbGVzIGFuZCB0YXhhIChvbiB0aGUgYmFzaXMgb2YgdGF4b25vbXkgYW5kIG9mIGEgcHJldmFsZW5jZSBhbmQgYWJ1bmRhbmNlIGZpbHRlcikgYW5kIHJldHVybnMgdGhlIGZpbHRlcmVkIGBwaHlsb3NlcWAgb2JqZWN0cyBpbiBhIHNlcGFyYXRlIGxpc3QuIFRheG9ub21pYyBhZ2dsb21lcmF0aW9uIGlzIG9wdGlvbmFsbHkgY2FycmllZCBvdXQuIFRoZSBvcHRpb25zIGZvciBmaWx0ZXJpbmcgYXJlIHRob3NlIGRlZmluZWQgaW4gdGhlIG9wdGlvbnMgc2VjdGlvbi4gIAoKIyMjIEZpbHRlciBzYW1wbGVzLiAgCgpTYW1wbGVzIHdpdGggbGVzcyB0aGFuIGEgZ2l2ZW4gbnVtYmVyIG9mIHNlcXVlbmNlcyBhcmUgZGlzY2FyZGVkIGZpcnN0LiBBcyBhIGNvbnNlcXVlbmNlLCBhIGdpdmVuIGBwaHlsb3NlcWAgb2JqZWN0IG1heSBmYWxsIG91dCBiZWxvdyB0aGUgc2V0IG1pbmltdW0gb2Ygc2FtcGxlcy4gVGhlcmVmb3JlIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyBwZXIgb2JqZWN0IGlzIGNoZWNrZWQgYWdhaW4uCgpgYGB7ciBmaWx0ZXJfYW5kX2dsb21fc2FtcGxlcywgZHBpID0gOTZ9CmlmKGtlZXBfdGltZSkgdGljKCJmaWx0ZXIgYW5kIGdsb20gc2FtcGxlcyIpCiMgZmlyc3QgcHJ1bmUgb2JqZWN0cyB3aXRoIGxlc3MgdGhhbiBtaW5fc2VxcyBzZXF1ZW5jZXMKcGh5c2VxX2xpc3RfMCAgPC0gcGh5c2VxX2xpc3QKIyBjcmVhdGUgZmlyc3Qgc3RlcCBvZiB0aGUgZmlsdGVyaW5nIHJlcG9ydApmaWx0ZXJpbmdfcmVwb3J0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChwaHlzZXFfbGlzdCkpCmZpbHRlcmluZ19yZXBvcnRfMCA8LSBtYXAocGh5c2VxX2xpc3QsIHJlcG9ydF9zdGVwXzApCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdCkpewogIGNhdCgicHJvY2Vzc2luZyAiLCBpLCAiIG9mICIsIGxlbmd0aChwaHlzZXFfbGlzdCksICJcbiIpCiAgIyBzZXQgcGxvdF9lY2RmPUYgZm9yIGZhc3RlciBleGVjdXRpb24KICBwaHlzZXFfbGlzdF8wW1tpXV0gPC0gcHJ1bmVfc2FtcGxlc19ieV9zaXplKHBoeXNlcV9saXN0W1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcyhwaHlzZXFfbGlzdClbaV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdF9lY2RmID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5zZXFzID0gbWluX3NlcXMpCn0KY2F0KCIuLi5kb25lIHBydW5pbmcgc2FtcGxlcy4uLlxuIikKIyBub3cgcmVjaGVjayBpZiBhbGwgb2JqZWN0cyBoYXZlIGVub3VnaCBzYW1wbGVzCnBoeXNlcV9saXN0XzEgPC0gcmVtX2xvd19zYW1wbGVfb2JqKHBoeXNlcV9saXN0XzAsIG1zID0gbWluX3NhbXBsZXMpCiMgY3JlYXRlIHNlY29uZCBzdGVwIG9mIHRoZSBmaWx0ZXJpbmcgcmVwb3J0CnN0YWdlX25hbWUgPSAicHJ1bmUgc2FtcGxlcyIKZm9yKGkgaW4gc2VxX2Fsb25nKHBoeXNlcV9saXN0XzApKXsKICBpZihuYW1lcyhwaHlzZXFfbGlzdF8wKVtpXSAlaW4lIG5hbWVzKHBoeXNlcV9saXN0XzEpKXsKICAgIG5hbWUgPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KICAgIHN0YWdlIDwtIHJlcG9ydF9zdGVwX24obXlfcGh5c2VxID0gcGh5c2VxX2xpc3RfMVtbbmFtZV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbXlfcGh5c2VxX28gPSBwaHlzZXFfbGlzdF8wW1tpXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFnZV9uYW1lID0gc3RhZ2VfbmFtZSkgCiAgfSBlbHNlIHsKICAgIHN0YWdlIDwtIGMoc3RhZ2VfbmFtZSwKICAgICAgICAgICAgICBzYW1wbGVzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgc2VxdWVuY2VzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgdGF4YSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2VxID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgcHJvcF90YXhhID0gTkFfcmVhbF8pCiAgfQogIGZpbHRlcmluZ19yZXBvcnRbW2ldXSA8LSByYmluZChzdGFnZV8wID0gYXMuY2hhcmFjdGVyKGZpbHRlcmluZ19yZXBvcnRfMFtbaV1dKSwgc3RhZ2VfMSA9c3RhZ2UpCiAgbmFtZXMoZmlsdGVyaW5nX3JlcG9ydClbaV0gPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KfQoKCmlmKHZlcmJvc2Vfb3V0cHV0KSBwcmludChmaWx0ZXJpbmdfcmVwb3J0KQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCiMjIFRheG9ub21pYyBmaWx0ZXJpbmcuICAKCkEgbnVtYmVyIG9mIHRheG9ub21pYyBmaWx0ZXJpbmcgb3BlcmF0aW9ucyBpcyBwZXJmb3JtZWQgYXQgdGhpcyBzdGFnZSAoZGVwZW5kaW5nIG9uIHRoZSBuYXR1cmUgb2YgdGhlIG9iamVjdCBhbmQgZmlsdGVyaW5nIG9wdGlvbnMsIHNldCBpbiB0aGUgc2V0dGluZ19vcHRpb25zIGNodW5rKS4gUGxvdHMgYXJlIG9wdGlvbmFsbHkgcHJvZHVjZWQgYW5kIHNhdmVkLiAgCgpgYGB7ciB0YXhvbm9taWNfZmlsdGVyaW5nLCBkcGkgPSA5Nn0KaWYoa2VlcF90aW1lKSB0aWMoInRheG9ub21pYyBmaWx0ZXJpbmcsIHN0ZXAgMSIpCgojIGNoZWNrIHRoZSBuYXR1cmUgb2YgdGhlIHRheG9ub21pYyB0YWJsZQojIGluIEZNQk4geW91IGVpdGhlciBoYXZlIEFTViB0YWJsZXMgb3IgdGF4IHRhYmxlcyB3aXRoIGFnZ2xvbWVyYXRpb24gYXQgdGhlIHNwZWNpZXMgbGV2ZWwgb3IgYWJvdmUKIyB3aGljaCBhcmUgdGhlIG5hbWVzIG9mIHRoZSB0YXggbGV2ZWxzPwp0YXhfbGV2ZWxzIDwtIGMoImRvbWFpbiIsInBoeWx1bSIsImNsYXNzIiwib3JkZXIiLCJmYW1pbHkiLCJnZW51cyIsInNwZWNpZXMiKQojIGdldC9zZXQgcmFuayBuYW1lcyAodGhpcyBpcyBuZWNlc3NhcnkgYmVjYXVzZSBwaHlsb3NlcSBvYmplY3RzIGZyb20gRk1CTiBvciBmcm9tIHRoZQojIGJpb2NvbmR1Y3RvciBwaXBlbGluZSB3aXRoIFNJTFZBIGhhdmUgZGlmZmVyZW50IG5hbWVzKQoKcGh5c2VxX2xpc3RfMiA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMSwgZ3NldF9yYW5rX25hbWVzLCB0YXhfbGV2ZWxzID0gdGF4X2xldmVscykKIyByZW1vdmUgdW5jaGFyYWN0ZXJpemVkIHRheGEgaW4gYWxsIHBoeWxvc2VxIG9iamVjdHMgKHVzaW5nIGEgZnVuY3Rpb25hbCkKcGh5c2VxX2xpc3RfMyA8LSBwaHlzZXFfbGlzdF8yCmlmKHJtX3VuY2hhcil7CiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMiwgc3Vic2V0X3RheGEsICFpcy5uYShkb21haW4pICYgIWRvbWFpbiAlaW4lIGMoIiIsICJ1bmNoYXJhY3Rlcml6ZWQiKSkKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCBzdWJzZXRfdGF4YSwgIWlzLm5hKHBoeWx1bSkgJiAhcGh5bHVtICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKQp9CgojIG9wdGlvbmFsbHkgcmVtb3ZlIEV1a2FyeW90ZXMKaWYocm1fZXVrKSB7CiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMywgc3Vic2V0X3RheGEsIGRvbWFpbiAhPSJFdWthcnlvdGEiKQp9CgojIG9wdGlvbmFsbHkgcmVtb3ZlIGNobG9yb3BsYXN0cyBhbmQgbWl0b2Nob25kcmlhIAppZihybV9jaGxtaXQpIHsKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCByZW1vdmVfQ2hsX01pdCkKfQoKIyB1c2UgbG9va3VwIHRhYmxlIHRvIGNoYW5nZSB0YXhvbm9teSBvZiBMYWN0b2JhY2lsbHVzOyBuZWNlc3NhcnkgZm9yIHBoeWxvc2VxIG9iamVjdHMgcHJvZHVjZWQgd2l0aCBTSUxWQQojIGJ1dCBub3QgZm9yIG9iamVjdHMgZXh0cmFjdGVkIGZyb20gRk1CTiAod2hpY2ggYXJlIHRyYW5zZm9ybWVkIGJlZm9yZSBleHRyYWN0aW9uKTsgd2lsbCBhbHNvIGNoYW5nZSB0aGUgCiMgdGF4YSBuYW1lcyBmb3IgQVNWcwojIE1BWSBCRSBTTE9XCmlmKHVzZV9sb29rdXApewogICMgbG9vcCBvdmVyIHRoZSBsaXN0IG9mIHBoeWxvc2VxIG9iamVjdHMKICBmb3IoaSBpbiBzZXFfYWxvbmcocGh5c2VxX2xpc3RfMykpewogICAgIyBjaGVjayB0aGUgbGVuZ3RoIG9mIHRoZSBuYW1lcyBvZiB0aGUgdGF4YTsgaWYgPDEwMCBpdCBpcyBmcm9tIEZNQk4sIGJyZWFrCiAgICBpZihtZWFuKHNhcHBseSh0YXhhX25hbWVzKHBoeXNlcV9saXN0XzNbW2ldXSksIG5jaGFyKSxuYS5ybSA9IFQpPDEwMCl7CiAgICAgIG5leHQKICAgIH0gZWxzZSB7CiAgICAgICMgY2hhbmdlIHRoZSBuYW1lcwogICAgICB0bmFtZXMgPC0gc3RyX2MoIkFTViIsc2VxKDE6bnRheGEocGh5c2VxX2xpc3RfM1tbaV1dKSkpCiAgICAgIHRheGFfbmFtZXMocGh5c2VxX2xpc3RfM1tbaV1dKTwtdG5hbWVzCiAgICAgICMgZ2V0IHNwZWNpZXMgdG8gY2hhbmdlCiAgICAgIHRheGFfdGFibGUgPC0gYXMuZGF0YS5mcmFtZShhcyh0YXhfdGFibGUocGh5c2VxX2xpc3RfM1tbaV1dKSwibWF0cml4IikpCiAgICAgIHRheGFfdGFibGUgPC0gdGF4YV90YWJsZSAlPiUgbXV0YXRlKGlkID0gc3RyX2MoZ2VudXMsIHNwZWNpZXMsIHNlcCA9ICIgIikpCiAgICAgIHRheGFfdGFibGUgPC0gbGVmdF9qb2luKHRheGFfdGFibGUsIGxvb2t1cF90YWJsZSkKICAgICAgbl9jaGFuZ2VzIDwtIHN1bSghaXMubmEodGF4YV90YWJsZSRuZXdfc3BlY2llcykpCiAgICAgIHRheGFfdGFibGUgPC0gdGF4YV90YWJsZSAlPiUgbXV0YXRlKHNwZWNpZXMgPSBpZl9lbHNlKCFpcy5uYShuZXdfc3BlY2llcyksIG5ld19zcGVjaWVzLCBzcGVjaWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VudXMgPSBpZl9lbHNlKCFpcy5uYShuZXdfZ2VudXMpLCBuZXdfZ2VudXMsIGdlbnVzKQogICAgICApCiAgICAgICMgcmVtb3ZlIGNvbHVtbnMKICAgICAgdGF4YV90YWJsZSA8LSBkcGx5cjo6c2VsZWN0KHRheGFfdGFibGUsIGRvbWFpbjpzcGVjaWVzKQogICAgICAjIG5vdyBjaGFuZ2UgZ2VudXMgZm9yIExhY3RvYmFjaWxsdXMgd2l0aCBubyBzcGVjaWVzCiAgICAgIHRvX2NoYW5nZV9sYiA8LSB3aGljaCgodGF4YV90YWJsZSRnZW51cyA9PSAiTGFjdG9iYWNpbGx1cyIpICYgaXMubmEodGF4YV90YWJsZSRzcGVjaWVzKSkKICAgICAgdGF4YV90YWJsZSRnZW51c1t0b19jaGFuZ2VfbGJdPC0iTGFjdG9iYWNpbGx1cyBjb21wbGV4IgogICAgICAjIHJlcGxhY2UgTGV1Y29ub3N0b2NhY2VhZSB3aXRoIExhY3RvYmFjaWxsYWNlYWUKICAgICAgbl9sZXVjIDwtIG5yb3coZHBseXI6OmZpbHRlcih0YXhhX3RhYmxlLCBmYW1pbHkgPT0gIkxldWNvbm9zdG9jYWNlYWUiKSkKICAgICAgdGF4YV90YWJsZSA8LSB0YXhhX3RhYmxlICU+JSBtdXRhdGUoZmFtaWx5ID0gaWZlbHNlKGZhbWlseSA9PSAiTGV1Y29ub3N0b2NhY2VhZSIsICJMYWN0b2JhY2lsbGFjZWFlIiwgZmFtaWx5KSkKICAgICAgdGF4YV90YWJsZSA8LSBhcy5tYXRyaXgodGF4YV90YWJsZSkKICAgICAgcm93bmFtZXModGF4YV90YWJsZSk8LXRuYW1lcwogICAgICB0YXhfdGFibGUocGh5c2VxX2xpc3RfM1tbaV1dKTwtdGF4YV90YWJsZQogICAgICBpZih2ZXJib3NlX291dHB1dCl7CiAgICAgICAgY2F0KG5hbWVzKHBoeXNlcV9saXN0XzMpW2ldLCI6IGNoYW5nZWQgIiwgbl9jaGFuZ2VzK25fbGV1YywgIiB0YXhhXG4iLCBzZXAgPSIiKQogICAgICB9CiAgICB9CiAgfQp9CgojIHJlbW92ZSBmdXJ0aGVyIHRheGEgd2hpY2ggYXJlIHVuY2hhcmFjdGVyaXplZCBhdCB0aGUgZmFtaWx5IHRvIGNsYXNzIGxldmVsCgojIG5vdGUgZm9yIHNlbGY6IG1pZ2h0IGltcHJvdmUgaXQgYnkgc2V0dGluZyB0aGUgbGV2ZWwgYXQgb3IgYWJvdmUgd2hpY2ggdW5jaGFyYWN0ZXJpemVkIHRheGEgY2FuIGJlIHJlbW92ZWQKIyByYXRoZXIgdGhhbiB1c2luZyBhIFQvRiBmbGFnCmlmKGFib3ZlX2dlbnVzX2ZsYWcpewogIHBoeXNlcV9saXN0XzMgPC0gbGFwcGx5KHBoeXNlcV9saXN0XzMsIHN1YnNldF90YXhhLCAhaXMubmEoY2xhc3MpICYgIWNsYXNzICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKSAjIENsYXNzCiAgcGh5c2VxX2xpc3RfMyA8LSBsYXBwbHkocGh5c2VxX2xpc3RfMywgc3Vic2V0X3RheGEsICFpcy5uYShvcmRlcikgJiAhb3JkZXIgJWluJSBjKCIiLCAidW5jaGFyYWN0ZXJpemVkIikpICMgT3JkZXIKICBwaHlzZXFfbGlzdF8zIDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCBzdWJzZXRfdGF4YSwgIWlzLm5hKGZhbWlseSkgJiAhZmFtaWx5ICVpbiUgYygiIiwgInVuY2hhcmFjdGVyaXplZCIpKSAjIEZhbWlseQp9CgojIG9wdGlvbmFsbHkgcGVyZm9ybSB0YXhvbm9taWMgYWdnbG9tZXJhdGlvbiwgCgojIG1heSB0YWtlIHNvbWUgdGltZTsgY2FuIGJlIG1hZGUgZmFzdGVyIHdpdGggcGx5ciBmdW5jdGlvbnMgdXNpbmcgcGFyYWxsZWxpemF0aW9uIG9yIHdpdGggZnVycnIKcGh5c2VxX2xpc3RfNCA8LSBwaHlzZXFfbGlzdF8zCmlmKHRheGdsb20gIT0gIm5vbmUiKXsKICBwaHlzZXFfbGlzdF80IDwtIGxhcHBseShwaHlzZXFfbGlzdF8zLCB0YXhfZ2xvbV9uYW1lX2NoYW5nZSwgdGF4YV9nbG9tID0gdGF4Z2xvbSkKfQoKIyBjcmVhdGUgdGhpcmQgc3RlcCBvZiB0aGUgZmlsdGVyaW5nIHJlcG9ydApzdGFnZV9uYW1lID0gInRheG9ub21pYyBmaWx0ZXIrZ2xvbSIKZm9yKGkgaW4gc2VxX2Fsb25nKHBoeXNlcV9saXN0XzApKXsKICBpZihuYW1lcyhwaHlzZXFfbGlzdF8wKVtpXSAlaW4lIG5hbWVzKHBoeXNlcV9saXN0XzEpKXsKICAgIG5hbWUgPC0gbmFtZXMocGh5c2VxX2xpc3RfMClbaV0KICAgIHN0YWdlIDwtIHJlcG9ydF9zdGVwX24obXlfcGh5c2VxID0gcGh5c2VxX2xpc3RfNFtbbmFtZV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbXlfcGh5c2VxX28gPSBwaHlzZXFfbGlzdF8wW1tpXV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFnZV9uYW1lID0gc3RhZ2VfbmFtZSkgCiAgfSBlbHNlIHsKICAgIHN0YWdlIDwtIGMoc3RhZ2VfbmFtZSwKICAgICAgICAgICAgICBzYW1wbGVzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgc2VxdWVuY2VzID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgdGF4YSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3Bfc2VxID0gTkFfcmVhbF8sCiAgICAgICAgICAgICAgcHJvcF90YXhhID0gTkFfcmVhbF8pCiAgfQogIGZpbHRlcmluZ19yZXBvcnRbW2ldXSA8LSByYmluZChmaWx0ZXJpbmdfcmVwb3J0W1tpXV0sIHN0YWdlXzMgPXN0YWdlKQp9CgppZihrZWVwX3RpbWUpIHRvYygpCgppZihrZWVwX3RpbWUpIHRpYygiY2FsY3VsYXRlIGRpdmVyc2l0eSBwcmUtZmlsdGVyIikKCiMgQ2FsY3VsYXRlIGRpdmVyc2l0eSBwcmlvciB0byBmaWx0ZXJpbmcgZm9yIHByZXZhbGVuY2UgYW5kIGFidW5kYW5jZQoKZGl2X2VzdF9wcmVmaWx0ZXIgPC0gbWFwX2RmcihwaHlzZXFfbGlzdF80LCBwaHlsb3NlcTo6ZXN0aW1hdGVfcmljaG5lc3MsIHNwbGl0ID0gRiwgbWVhc3VyZT1jKCJPYnNlcnZlZCIsIkNoYW8xIiwiU2hhbm5vbiIpKQpkaXZfZXN0X3ByZWZpbHRlciA8LSBtdXRhdGUoZGl2X2VzdF9wcmVmaWx0ZXIsIFBpZWxvdV9KID0gU2hhbm5vbi9sb2coT2JzZXJ2ZWQpKQojIGNhbGN1bGF0ZSBhbmQgYWRkIGF2ZSBCcmF5LUN1cnRpcyBkaXNzaW1pbGFyaXR5Cm1lYW5iY2Rpc3QgPC0gbWFwKHBoeXNlcV9saXN0XzQsIHBoeWxvc2VxOjpkaXN0YW5jZSwgbWV0aG9kPSJicmF5IikKZGl2X2VzdF9wcmVmaWx0ZXIkYXZlX0JDIDwtIHVubGlzdChtYXAobWVhbmJjZGlzdCwgbWVhbikpCgpyb3cubmFtZXMoZGl2X2VzdF9wcmVmaWx0ZXIpIDwtIG5hbWVzKHBoeXNlcV9saXN0XzQpCiMgc2F2ZSBmb3IgZnVydGhlciB1c2UKc2F2ZShkaXZfZXN0X3ByZWZpbHRlciwgCiAgICAgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9kaXZwcmVmaWx0ZXIuUmRhdGEiLHNlcD0iIikpCgppZihrZWVwX3RpbWUpIHRvYygpCgojIFByZXZhbGVuY2UgYW5kIGFidW5kYW5jZSBmaWx0ZXIKaWYoa2VlcF90aW1lKSB0aWMoInRheG9ub21pYyBmaWx0ZXJpbmcsIHN0ZXAgMiIpCgojIE5PVEUgdXNpbmcgYSBwcmV2YWxlbmNlIGZpbHRlciBiYXNlZCBvbiBmcmFjdGlvbiBtYXkgYmUgd3JvbmcgZm9yIHN0dWRpZXMgd2l0aCBsYXJnZSAKIyBudW1iZXIgb2Ygc2FtcGxlcywgaW4gd2hpY2ggb25lIG1pZ2h0IHdhbnQgdG8gcmV0YWluIHRheGEgd2hpY2ggYXBwZWFyIGluID4xMCBzYW1wbGVzIAoKcGh5c2VxX2xpc3RfNSA8LSBwaHlzZXFfbGlzdF80CiMgd2lsbCBiZSBza2lwcGVkIGlmIGZpbHRlck9UVXMgPT0gRgpub2RlX3N0YXRfbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgocGh5c2VxX2xpc3RfNSkpCiMgYSBsaXN0IHdoaWNoIHdpbGwgaG9zdCBub2RlIHN0YXRzLCBsaWtlIHRoZSBwcmV2YWIgZGF0YSwgaW5pdGlhbGx5IGVtcHR5LAojIGlmIG5vdGhpbmcgaXMgYWRkZWQgYXQgdGhpcyBzdGFnZSwgbm9kZSBzdGF0cyB3aWxsIGJlIGFkZGVkIGF0IGEgbGF0ZXIgc3RhZ2UKaWYoZmlsdGVyT1RVcyl7CiAgcHJldl9hYl9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChwaHlzZXFfbGlzdF80KSkKICAjIHdpbGwgaG9zdCB0aGUgbGlzdHMgd2l0aCB0aGUgcmVzdWx0cwogIGZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF80KSl7CiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UgZmlsdGVyLCBwaHlzZXEgIixpLCIgb2YgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKHBoeXNlcV9saXN0XzQpLCJcbiIpCiAgICBwcmV2X2FiX2xpc3RbW2ldXSA8LSBmaWx0ZXJfYnlfcHJldl9hYigKICAgICAgbXlwaHlzZXEgPSBwaHlzZXFfbGlzdF80W1tpXV0sCiAgICAgIG5hbWUgPSBuYW1lcyhwaHlzZXFfbGlzdF80KVtpXQogICAgKQogICAgbmFtZXMocHJldl9hYl9saXN0KVtpXTwtbmFtZXMocGh5c2VxX2xpc3RfNClbaV0KICAgICMgc2F2ZSB0aGUgcHJvY2Vzc2VkIHBoeWxvc2VxCiAgICBwaHlzZXFfbGlzdF81W1tpXV0gPC0gcHJldl9hYl9saXN0W1tpXV1bWzFdXQogICAgIyBzYXZlIHByZXYgYWIgdGFibGUKICAgIG5vZGVfc3RhdF9saXN0W1tpXV0gPC0gcHJldl9hYl9saXN0W1tpXV1bWzRdXQogICAgbmFtZXMobm9kZV9zdGF0X2xpc3QpW2ldIDwtIG5hbWVzKHBoeXNlcV9saXN0XzQpW2ldCiAgfQp9CiMgb3B0aW9uYWxseSBzYXZlIHRoZSBwcmV2X2FiX2xpc3QKaWYoc2F2ZV9wcmV2X2FiX2xpc3QpIHNhdmUocHJldl9hYl9saXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgbmFtZSwgIl9wcmV2YWJsLlJkYXRhIixzZXA9IiIpKQoKIyBjcmVhdGUgZm91cnRoIHN0ZXAgb2YgdGhlIGZpbHRlcmluZyByZXBvcnQKc3RhZ2VfbmFtZSA9ICJwcmV2YWxlbmNlIGFuZCBhYnVuZGFuY2UiCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF8wKSl7CiAgaWYobmFtZXMocGh5c2VxX2xpc3RfMClbaV0gJWluJSBuYW1lcyhwaHlzZXFfbGlzdF8xKSl7CiAgICBuYW1lIDwtIG5hbWVzKHBoeXNlcV9saXN0XzApW2ldCiAgICBzdGFnZSA8LSByZXBvcnRfc3RlcF9uKG15X3BoeXNlcSA9IHBoeXNlcV9saXN0XzVbW25hbWVdXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG15X3BoeXNlcV9vID0gcGh5c2VxX2xpc3RfMFtbaV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhZ2VfbmFtZSA9IHN0YWdlX25hbWUpIAogIH0gZWxzZSB7CiAgICBzdGFnZSA8LSBjKHN0YWdlX25hbWUsCiAgICAgICAgICAgICAgc2FtcGxlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHNlcXVlbmNlcyA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHRheGEgPSBOQV9yZWFsXywKICAgICAgICAgICAgICBwcm9wX3NhbXBsZXMgPSBOQV9yZWFsXywKICAgICAgICAgICAgICBwcm9wX3NlcSA9IE5BX3JlYWxfLAogICAgICAgICAgICAgIHByb3BfdGF4YSA9IE5BX3JlYWxfKQogIH0KICBmaWx0ZXJpbmdfcmVwb3J0W1tpXV0gPC0gcmJpbmQoZmlsdGVyaW5nX3JlcG9ydFtbaV1dLCBzdGFnZV80ID1zdGFnZSkKfQoKIyByZW1vdmUgdW5uZWVkZWQgb2JqZWN0cwpybShwaHlzZXFfbGlzdF8xLCBwaHlzZXFfbGlzdF8zLCBwaHlzZXFfbGlzdF80LCBwcmV2X2FiX2xpc3QsIHRheGFfdGFibGUpCiMgY3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSByZXBvcnQKZmlsdGVyaW5nX3JlcG9ydF9kZiA8LSBtYXBfZGZyKGZpbHRlcmluZ19yZXBvcnQsIGFzLmRhdGEuZnJhbWUsIC5pZCA9ICJkYXRhc2V0IikKZmlsdGVyaW5nX3JlcG9ydF9kZiA8LSBmaWx0ZXJpbmdfcmVwb3J0X2RmICU+JSAKICBtdXRhdGUoZGF0YV90eXBlID0gaWZfZWxzZShzdHJfZGV0ZWN0KGRhdGFzZXQsICJGTUJOIiksICJGTUJOIiwgIkFTViIpKSAlPiUKICBtdXRhdGUoZHBseXI6OmFjcm9zcyguY29scyA9IHNhbXBsZXM6cHJvcF90YXhhLCBhcy5udW1lcmljKSkgJT4lCiAgbXV0YXRlKHN0YWdlID0gYXNfZmFjdG9yKHN0YWdlKSkKaWYodmVyYm9zZV9vdXRwdXQpIHsKICBwcmludChmaWx0ZXJpbmdfcmVwb3J0X2RmKQogIHByaW50KGZpbHRlcmluZ19yZXBvcnRfZGYgJT4lCiAgICAgICAgICBkcGx5cjo6ZmlsdGVyKHN0YWdlICE9ICJvcmlnaW5hbCIgJiBzdGFnZSAhPSAicHJ1bmUgc2FtcGxlcyIpICU+JQogICAgICAgICAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRhdGFfdHlwZSwgeSA9IHByb3BfdGF4YSkpICsKICAgICAgICAgIGZhY2V0X3dyYXAofnN0YWdlKSArCiAgICAgICAgICBnZW9tX2JveHBsb3QoKSArCiAgICAgICAgICBsYWJzKHggPSAiZGF0YSB0eXBlIiwgeSA9ICJwcm9wLiB0YXhhIGxlZnQiKSkKICBzdW1tX2ZpbHRlcmluZyA8LSBmaWx0ZXJpbmdfcmVwb3J0X2RmICU+JQogICAgZ3JvdXBfYnkoZGF0YV90eXBlLCBzdGFnZSkgJT4lCiAgICBzdW1tYXJpemUobWluX3NlcSA9IG1pbihwcm9wX3NlcSksIAogICAgICAgICAgICAgIG1heF9zZXEgPSBtYXgocHJvcF9zZXEpLAogICAgICAgICAgICAgIG1pbl90YXhhID0gbWluKHByb3BfdGF4YSksCiAgICAgICAgICAgICAgbWF4X3RheGEgPSBtYXgocHJvcF90YXhhKSkKICBwcmludChzdW1tX2ZpbHRlcmluZykKfQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKClRoZSByZWR1Y3Rpb24gaW4gbnVtYmVyIG9mICJ0YXhhIiBpcyBkcmFtYXRpYyBpbiBtb3N0IGNhc2VzIChhIHByb3BvcnRpb24gZnJvbSAwLjAxMCB0byAwLjIxIGxlZnQpLCB3aGlsZSB0aGUgcHJvcG9ydGlvbiBvZiBzZXF1ZW5jZXMgbGVmdCBpcyBiZXR3ZWVuIDAuOTg0IGFuZCAwLjk5OS4KCiMgSW5mZXJyaW5nIHRoZSBuZXR3b3Jrcy4KCkkgd2lsbCBub3cgdHJ5IE1pY3JvYmlhbCBBc3NvY2lhdGlvbiBOZXR3b3JrIGluZmVyZW5jZSwgd2l0aCB0aGUgbWV0aG9kcyBhbmQgcGFyYW1ldGVycyBzcGVjaWZpZWQgaW4gdGhlIG9wdGlvbnMgY2h1bmsuIFNlcGFyYXRlIGxpc3RzIHdpbGwgYmUgZ2VuZXJhdGVkIGZvciBlYWNoIG1ldGhvZC4gIApBbiBlcnJvciB0cmFwcGluZyByb3V0aW5lIGhhcyBiZWVuIGltcGxlbWVudGVkOiBpZiBNQU4gZXN0aW1hdGlvbiBmYWlscyBhIHRyeS1lcnJvciBvYmplY3QgcmF0aGVyIHRoYW4gYW4gb2JqZWN0IG9mIGNsYXNzIG1pY3JvTmV0IHdpbGwgYmUgcmV0dXJuZWQuICAKQWxsIHRoZSByZXR1cm5lZCBvYmplY3RzIHdpbGwgYmUgc2F2ZWQgaW4gYSBsaXN0ICgxIHNsb3QgZm9yIGVhY2ggcGh5bG9zZXEgb2JqZWN0LCBlYWNoIHdpdGggMSBzbG90IGZvciBlYWNoIGluZmVyZW5jZSBtZXRob2QpLiAgCgpgYGB7ciBNQU5faW5mZXJlbmNlXzEsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiTWljcm9iaWFsIGFzc29jaWF0aW9uIG5ldHdvcmsgaW5mZXJlbmNlIikKaWYodmVyYm9zZV9vdXRwdXQpIGNhdCgiUGxlYXNlIGJlIHBhdGllbnQsIHRoaXMgd2lsbCB0YWtlIGEgd2hpbGUuLi5cbiIpCiMgbGlzdCBmb3IgcmVzdWx0cywgMSBzbG90IGZvciBlYWNoIG9iamVjdApNQU5faW5mX3Jlc3VsdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHBoeXNlcV9saXN0XzUpKQojIGNyZWF0ZSBsaXN0IGZvciBtZXRob2RzLCAxIHNsb3QgZm9yIGVhY2ggbWV0aG9kCmluZl9tZXRoX2xpc3QgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKGluZl9tZXRob2RzKSkKCmZvcihpIGluIHNlcV9hbG9uZyhwaHlzZXFfbGlzdF81KSl7CiAgbmFtZSA8LSBuYW1lcyhwaHlzZXFfbGlzdF81KVtpXQogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoIlxuIiwgIkluZmVycmluZyBuZXR3b3JrKHMpIGZvciAiLCBuYW1lLCAiLCAiLCBpLCAib2YgIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgocGh5c2VxX2xpc3RfNSksICJcbiIsIHNlcD0iICIpCiAgaWYoa2VlcF90aW1lKSB7CiAgICB0aWNtZXNzYWdlX29iamVjdCA8LSBwYXN0ZSgibWljcm9iaWFsIGFzc29jaWF0aW9uIG5ldHdvcmsgaW5mZXJlbmNlLCAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSwgIiwgIiwgaSwgIiBvZiAiLCBsZW5ndGgocGh5c2VxX2xpc3RfNSksIHNlcCA9ICIiKQogICAgdGljKHRpY21lc3NhZ2Vfb2JqZWN0KQogIH0KICBmb3IoaiBpbiBzZXFfYWxvbmcoaW5mX21ldGhvZHMpKXsKICAgIGlmKGtlZXBfdGltZSl7CiAgICAgIHRpY21lc3NhZ2VfbWV0aG9kIDwtIHBhc3RlKCJcbiIsICJpbmZlcmVuY2Ugd2l0aCBtZXRob2QgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5mX21ldGhvZHNbal0sICIsICIsIGosICIgb2YgIiwgbGVuZ3RoKGluZl9tZXRob2RzKSwgc2VwID0gIiIpCiAgICAgIHRpYyh0aWNtZXNzYWdlX21ldGhvZCkKICAgIH0KICAgIGluZm1ldGhvZCA8LSBpbmZfbWV0aG9kc1tbal1dCiAgICBpbmZwYXJhbSA8LSBpbmZfbWV0aG9kc19wYXJhbVtbal1dCiAgIAogICAgIyBpbmZyZW5jZSBpcyBjYXJyaWVkIG91dCBoZXJlIHVzaW5nIGEgdXNlciBkZWZpbmVkIGZ1bmN0aW9uIGxvYWRlZCB3aXRoIHRoZSBgc291cmNlKClgIGNvbW1hbmQgCiAgICAjIHlvdSBkbyBub3QgbmVlZCB0byBwcm92aWRlIG11Y2ggZGV0YWlsLCBldmVyeXRoaW5nIGlzIHRha2VuIGNhcmUgb2YgaW4gdGhlIG9wdGlvbnMKICAgICMgb2YgY291cnNlIGNvdWxkIGJlIGN1c3RvbWl6ZWQgaW4gdGhlIGZ1bmN0aW9uIGNhbGwKICAgIGluZl9tZXRoX2xpc3RbW2pdXSA8LSBpbmZlcl9NQU4obXlwaHlzZXEgPSBwaHlzZXFfbGlzdF81W1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluZl9tZXRob2QgPSBpbmZtZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZF9wYXJhbWV0ZXJzID0gaW5mcGFyYW0pCiAgICBuYW1lcyhpbmZfbWV0aF9saXN0KVtqXSA8LSBpbmZtZXRob2QKICAgIGlmKGtlZXBfdGltZSkgdG9jKCkKICB9CiAgTUFOX2luZl9yZXN1bHRzW1tpXV0gPC0gaW5mX21ldGhfbGlzdAogIG5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0gPC0gbmFtZQogIGlmKGtlZXBfdGltZSkgdG9jKCkKfQojIGNyZWF0ZSBhIHJlcG9ydAppbmZlcmVuY2VfcmVwb3J0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aChNQU5faW5mX3Jlc3VsdHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcoTUFOX2luZl9yZXN1bHRzKSl7CiAgaW5mZXJlbmNlX3JlcG9ydFtbaV1dPC1tYXBfZGZyKE1BTl9pbmZfcmVzdWx0c1tbaV1dLCBjbGFzcywgLmlkID0gIm1ldGhvZCIpCiAgbmFtZXMoaW5mZXJlbmNlX3JlcG9ydClbaV08LW5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KfQppbmZlcmVuY2VfcmVwb3J0X2RmIDwtIGJpbmRfcm93cyhpbmZlcmVuY2VfcmVwb3J0LCAuaWQgPSAiZGF0YXNldCIpCgojIHNhdmUgdGhlIGxpc3QgYW5kIGRvIHNvbWUgY2xlYW4tdXAKc2F2ZShNQU5faW5mX3Jlc3VsdHMsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0X2ZvbGRlciwgcGFzdGUob3V0X2ZpbGVuYW1lX3ByZWYsIl9NQU5saXN0LlJkYXRhIikpKQpybShpbmZfbWV0aF9saXN0LCBpbmZlcmVuY2VfcmVwb3J0KQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCiMgQW5hbHl6ZSB0aGUgbmV0d29ya3MuICAKClRoZSBpbmZlcnJlZCBuZXR3b3JrcyB3aWxsIGJlIGFuYWx5emVkIHVzaW5nIE5ldENvTWk6Om5ldEFuYWx5emUoKSBhbmQgdGhlIHJlc3VsdGluZyBvYmplY3RzIHdpbGwgYmUgcHJvY2Vzc2VkIGZ1cnRoZXIuICAKTmV0d29yaywgbm9kZSBhbmQgZWRnZSBzdGF0cyB3aWxsIGJlIGV4dHJhY3RlZCB0byBkYXRhIGZyYW1lcy90aWJibGVzIGZvciBmdXJ0aGVyIHByb2Nlc3NpbmcuICAKQSBmZXcgZXh0cmEgYW5hbHlzZXMgd2lsbCBiZSBjYXJyaWVkIG91dCB1c2luZyBwYWNrYWdlIHBoeWxvc2VxIHRvIGFkZCBkaXZlcnNpdHkgYW5kIGV2ZW5uZXNzIGluZGljZXMgdG8gdGhlIG1ldGFkYXRhLiAgCgpgYGB7ciBhbmFseXplX25ldHdvcmtzLCBkcGkgPSA5Nn0KCmlmKGtlZXBfdGltZSkgdGljKCJjYWxjdWxhdGUgZGl2ZXJzaXR5IHBvc3QtZmlsdGVyIikKCiMgZXN0aW1hdGUgZGl2ZXJzaXR5IGZvciBlYWNoIG9iamVjdCBvZiB0aGUgcGh5c2VxX2xpc3RfNSwgcmV0dXJucyBhIGxpc3Qgd2l0aCB0aGUgcmVzdWx0cwojIGV4dHJhY3QgYW5kIHB1dCB0b2dldGhlciB3aXRoIG1ldGFkYXRhCiMgd2lsbCBnZW5lcmF0ZSB3YXJuaW5ncwoKZGl2X2VzdF9wb3N0ZmlsdGVyIDwtIG1hcF9kZnIocGh5c2VxX2xpc3RfNSwgZXN0aW1hdGVfcmljaG5lc3MsIHNwbGl0ID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYXN1cmVzID0gYygiT2JzZXJ2ZWQiLCJDaGFvMSIsIlNoYW5ub24iKSwgLmlkID0gImxhYmVsIikKZGl2X2VzdF9wb3N0ZmlsdGVyIDwtIG11dGF0ZShkaXZfZXN0X3Bvc3RmaWx0ZXIsIFBpZWxvdV9KID0gU2hhbm5vbi9sb2coT2JzZXJ2ZWQpKQoKIyBjYWxjdWxhdGUgYW5kIGFkZCBhdmVyYWdlIEJyYXktQ3VydGlzIGRpc3NpbWlsYXJpdHkKbWVhbmJjZGlzdCA8LSBtYXAocGh5c2VxX2xpc3RfNSwgcGh5bG9zZXE6OmRpc3RhbmNlLCBtZXRob2Q9ImJyYXkiKQpkaXZfZXN0X3Bvc3RmaWx0ZXIkYXZlX0JDIDwtIHVubGlzdChtYXAobWVhbmJjZGlzdCwgbWVhbikpCgpyb3cubmFtZXMoZGl2X2VzdF9wb3N0ZmlsdGVyKSA8LSBuYW1lcyhwaHlzZXFfbGlzdF81KQojIHNhdmUgZm9yIGZ1cnRoZXIgdXNlCnNhdmUoZGl2X2VzdF9wb3N0ZmlsdGVyLCAKICAgICBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX2RpdnBvc3RmaWx0ZXIuUmRhdGEiLHNlcD0iIikpCgojIGFuIGFsdGVybmF0aXZlIGNvdWxkIGJlIHRvIHVzZSBpdCBvbiBkaXZfZXN0X3ByZWZpbHRlcgpzdHVkeV9tZXRhZGF0YSA8LSBsZWZ0X2pvaW4oc3R1ZHlfbWV0YWRhdGEsIGRpdl9lc3RfcG9zdGZpbHRlcikKCmlmKGtlZXBfdGltZSkgdG9jKCkKCiMgY2FsY3VsYXRlIG5ldHdvcmsgc3RhdGlzdGljcyB3aXRoIG5ldEFuYWx5emUKaWYoa2VlcF90aW1lKSB0aWMoIkNhbGN1bGF0ZSBuZXR3b3JrIHN0YXRpc3RpY3MiKQoKIyB0aGUgbGlzdCBmb3IgdGhlIG1ldGhvZHMgd2l0aGluIHRoZSBkYXRhc2V0Cm5ldF9zdGF0cyA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgoTUFOX2luZl9yZXN1bHRzKSkKIyBuZXRfc3RhdF9yZXN1bHRzIGlzIGEgbGlzdCwgZG8gdGhlIGNhbGN1bGF0aW9uIGZvciBlYWNoIG9mIHRoZSBkYXRhc2V0cywgYWxsIGluZmVyZW5jZSBtZXRob2RzCmZvcihpIGluIHNlcV9hbG9uZyhNQU5faW5mX3Jlc3VsdHMpKXsKICBuYW1lIDwtIG5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJDYWxjdWxhdGluZyBuZXR3b3JrIHN0YXRzIGZvciIsbmFtZSwiXG4iKQogIG5ldF9zdGF0X3Jlc3VsdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKE1BTl9pbmZfcmVzdWx0c1tbaV1dKSkKICBmb3IoaiBpbiBzZXFfYWxvbmcoTUFOX2luZl9yZXN1bHRzW1tpXV0pKXsKICAgIG10aGQgPC0gbmFtZXMoTUFOX2luZl9yZXN1bHRzW1tpXV0pW2pdCiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJtZXRob2QiLG10aGQsIlxuIikKICAgICMgY29uc2lkZXIgcmVkdWNpbmcgYXJndW1lbnQgaHViUXVhbnQgdG8gMC43NS0wLjkwIGRlZmF1bHQgaXMgMC45NSksCiAgICBuZXRfc3RhdF9yZXN1bHRzW1tqXV0gPC0gY2FsY3VsYXRlX25ldF9zdGF0cyhNQU5faW5mX3Jlc3VsdHNbW2ldXVtbal1dKQogIH0KICBuYW1lcyhuZXRfc3RhdF9yZXN1bHRzKTwtbmFtZXMoTUFOX2luZl9yZXN1bHRzW1tpXV0pCiAgbmV0X3N0YXRzW1tpXV0gPC0gbmV0X3N0YXRfcmVzdWx0cwogIG5hbWVzKG5ldF9zdGF0cylbaV08LW5hbWVzKE1BTl9pbmZfcmVzdWx0cylbaV0KfQpybShuZXRfc3RhdF9yZXN1bHRzLCBtZWFuYmNkaXN0LCBtdGhkKQppZihrZWVwX3RpbWUpIHRvYygpCgojIGV4dHJhY3RpbmcgZ2xvYmFsIG5ldHdvcmsgcHJvcGVydGllcwoKaWYoa2VlcF90aW1lKSB0aWMoIkV4dHJhY3Rpb24gZ2xvYmFsIG5ldHdvcmsgcHJvcGVydGllcyIpCiMgZXh0cmFjdGluZyB0aGUgZ2xvYmFsIG5ldHdvcmsgc3RhdHMgCmdsb2JhbF9wcm9wc19saXN0X2EgPC12ZWN0b3IoImxpc3QiLGxlbmd0aChuZXRfc3RhdHMpKQpnbG9iYWxfcHJvcHNfbGlzdF9sIDwtdmVjdG9yKCJsaXN0IixsZW5ndGgobmV0X3N0YXRzKSkgCmdsb2JhbF9wcm9wc19saXN0X2FsbCA8LXZlY3RvcigibGlzdCIsbGVuZ3RoKGluZl9tZXRob2RzKSkKZ2xvYmFsX3Byb3BzX2xpc3RfbGNjIDwtdmVjdG9yKCJsaXN0IixsZW5ndGgoaW5mX21ldGhvZHMpKQpmb3IgKGkgaW4gc2VxX2Fsb25nKG5ldF9zdGF0cykpewogIGRhdGFzZXQgPC0gbmFtZXMobmV0X3N0YXRzKVtpXQogIGZvciAoaiBpbiBzZXFfYWxvbmcobmV0X3N0YXRzW1tpXV0pKXsKICAgIGlmKGNsYXNzKG5ldF9zdGF0c1tbaV1dW1tqXV0pIT0gIm1pY3JvTmV0UHJvcHMiKXsKICAgICAgbmV4dAogICAgfSBlbHNlewogICAgICBubm9kZXMgPC0gc3VtKG5ldF9zdGF0c1tbaV1dW1tqXV0kY2VudHJhbGl0aWVzJGRlZ3JlZTE+MCkKICAgICAgbnRheGEgPC0gbnJvdyhuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxKQogICAgICBucG9zZWRnZSA8LSBzdW0obmV0X3N0YXRzW1tpXV1bW2pdXSRpbnB1dCRhc3NvTWF0MVtsb3dlci50cmkobmV0X3N0YXRzW1tpXV1bW2pdXSRpbnB1dCRhc3NvTWF0MSldPjApCiAgICAgIG5uZWdlZGdlIDwtIHN1bShuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxW2xvd2VyLnRyaShuZXRfc3RhdHNbW2ldXVtbal1dJGlucHV0JGFzc29NYXQxKV08MCkKICAgICAgZXh0cmFfcHJvcF92ZWN0b3IgPC0gYyhubm9kZXMsIG50YXhhLCBucG9zZWRnZSwgbm5lZ2VkZ2UpCiAgICAgIG5hbWVzKGV4dHJhX3Byb3BfdmVjdG9yKSA8LSBjKCJubm9kZXMiLCAibnRheGEiLCAibnBvc2VkZ2UiLCAibm5lZ2VkZ2UiKQogICAgICBnbG9iYWxfcHJvcHNfbGlzdF9hbGxbW2pdXSA8LSBjKHVubGlzdChuZXRfc3RhdHNbW2ldXVtbal1dJGdsb2JhbFByb3BzKSxleHRyYV9wcm9wX3ZlY3RvcikKICAgICAgZ2xvYmFsX3Byb3BzX2xpc3RfbGNjW1tqXV0gPC0gYyh1bmxpc3QobmV0X3N0YXRzW1tpXV1bW2pdXSRnbG9iYWxQcm9wc0xDQyksZXh0cmFfcHJvcF92ZWN0b3IpCiAgICAgIG5hbWVzKGdsb2JhbF9wcm9wc19saXN0X2FsbClbal0gPC0gbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfbGNjKVtqXTwtIG5hbWVzKG5ldF9zdGF0c1tbaV1dW2pdKQogICAgfQogICAgIyBjcmVhdGUgZGF0YSBmcmFtZSB3aXRoIHJlc3VsdHMKICAgIGFsbF9kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfYWxsLCAuaWQgPSAibWV0aG9kIikKICAgIGxjY19kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfbGNjLCAuaWQgPSAibWV0aG9kIikgCiAgfQogIGdsb2JhbF9wcm9wc19saXN0X2FbW2ldXSA8LSBhbGxfZGYKICBnbG9iYWxfcHJvcHNfbGlzdF9sW1tpXV0gPC0gbGNjX2RmCiAgbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfYSlbaV0gPC0gbmFtZXMoZ2xvYmFsX3Byb3BzX2xpc3RfbClbaV0gPC0gbmFtZXMobmV0X3N0YXRzKVtpXQp9CiMgbm90ZSB0aGF0IHN0cl9zdWIgb25seSB3b3JrcyBpZiB5b3UgaGF2ZSA8PTkgaW5mZXJlbmNlIG1ldGhvZHMgaW4gaW5mX21ldGhvZHMKZ2xvYmFsX2FsbF9kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfYSwgLmlkID0gImRhdGFzZXQiKSAKZ2xvYmFsX2xjY19kZiA8LSBiaW5kX3Jvd3MoZ2xvYmFsX3Byb3BzX2xpc3RfbCwgLmlkID0gImRhdGFzZXQiKSAKCgpnbG9iYWxfYWxsX2RmIDwtIGxlZnRfam9pbihnbG9iYWxfYWxsX2RmLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHN0dWR5X21ldGFkYXRhLGxhYmVsLCBvYmpfdHlwZSwgdGFyZ2V0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lvbiwgcGxhdGZvcm0sIHNhbXBsZXMsIHR5cGUsIE9ic2VydmVkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENoYW8xLCBTaGFubm9uLCBQaWVsb3VfSiwgYXZlX0JDKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiZGF0YXNldCIgPSAibGFiZWwiKSkgJT4lCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgfm5hX2lmKC4sSW5mKSkpCmdsb2JhbF9sY2NfZGYgPC0gbGVmdF9qb2luKGdsb2JhbF9sY2NfZGYsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3Qoc3R1ZHlfbWV0YWRhdGEsbGFiZWwsIG9ial90eXBlLCB0YXJnZXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uLCBwbGF0Zm9ybSwgc2FtcGxlcywgdHlwZSwgT2JzZXJ2ZWQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2hhbzEsIFNoYW5ub24sIFBpZWxvdV9KLCBhdmVfQkMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJkYXRhc2V0IiA9ICJsYWJlbCIpKSAlPiUKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+bmFfaWYoLixJbmYpKSkKCiMgYm90aCBtaWdodCBjb250YWluIEluZiB3aGljaCBhcmUgcmVwbGFjZWQgYnkgTkEgKHVzaW5nIGRwbHlyOjpuYV9pZigpKSB0byBiZSBoYW5kbGVkCiMgY29ycmVjdGx5IGlmIGRvaW5nIFBDQSBieSBwYWlyd2lzZSBkZWxldGlvbi4KIyB0aGUgcHJvYmxlbSBvbmx5IG9jY3VycyBpbiBhdlBhdGgxIGFuZCBjbHVzdENvZWYxLCBidXQgSSBhbSBoYW5kbGluZyBpdCB3aXRoIGEgc2NvcGVkIG11dGF0ZQojIGEgYmV0dGVyIHNvbHV0aW9uIG1pZ2h0IGJlIHRoZSB1c2Ugb2YgaGFibGFyOjpyYXRpb25hbGl6ZSgpCgoKIyBzYXZlIHRoZSBkYXRhIGZyYW1lcyBmb3IgZnVydGhlciB1c2UKd3JpdGVfdHN2KGdsb2JhbF9hbGxfZGYsIGZpbGUgPSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfbmV0cHJvcGFsbC50eHQiLHNlcD0iIikpCndyaXRlX3RzdihnbG9iYWxfbGNjX2RmLCBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX25ldHByb3BsY2MudHh0IixzZXA9IiIpKQoKIyBwcmludCBhIHN1bW1hcnkgdGFibGUKZ2xvYmFsX2FsbF9kZgoKcm0oZ2xvYmFsX3Byb3BzX2xpc3RfYSwgZ2xvYmFsX3Byb3BzX2xpc3RfbCwgZ2xvYmFsX3Byb3BzX2xpc3RfYWxsLCAKICAgZ2xvYmFsX3Byb3BzX2xpc3RfbGNjLCBhbGxfZGYsIGxjY19kZiwgbm5vZGVzLCBudGF4YSwgbnBvc2VkZ2UsIG5uZWdlZGdlLCBleHRyYV9wcm9wX3ZlY3RvcikKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKYGBgCgpHbG9iYWwgbmV0d29yayBwcm9wZXJ0aWVzIGhhdmUgYmVlbiBzYXZlZCBhcyBkYXRhIGZyYW1lcyBmb3IgZnVydGhlciB1c2UuIFRoZXkgd2lsbCBiZSB1c2VkIHRvZ2V0aGVyIHRob3NlIGdlbmVyYXRlZCBmb3IgdGhlIG90aGVyIGRhdGEgc2V0cy4KCiMjIE5vZGUgcHJvcGVydGllcy4KCk5vZGUgcHJvcGVydGllcyBjYW4gYmUgZXh0cmFjdGVkIGZyb20gdGhlIG1pY3JvTmV0UHJvcHMgb2JqZWN0cyBhbmQgc2F2ZWQgZm9yIGZ1cnRoZXIgdXNlLiAgCgpgYGB7ciBleHRyYWN0X25vZGVzLCBkcGkgPSA5Nn0KCiMgZXh0cmFjdCBub2RlIHByb3BlcnRpZXMKIyB1c2luZyBhIGxvb3AgdGFrZXMgc2xpZ2h0bHkgbG9uZ2VyIHRoYXQgdXNpbmcgZnVuY3Rpb25hbHMgYnV0IGhhbmRsZXMgbmFtZXMgYmV0dGVyCiMgbm90ZSB0aGF0IHdoZW4gdXNpbmcgQVNWcyBjb21wYXJpbmcgbm9kZXMgYmV0d2VlbiBkYXRhc2V0cyBkb2VzIG5vdCBtYWtlIG11Y2ggc2Vuc2UKCmlmKGtlZXBfdGltZSkgdGljKCJFeHRyYWN0aW5nIG5vZGUgcHJvcGVydGllcyIpCgpub2RlX3N0YXRzIDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChuZXRfc3RhdHMpKQpmb3IgKGkgaW4gc2VxX2Fsb25nKG5ldF9zdGF0cykpIHsKICBub2RlX3Byb3BlcnRpZXMgPC0gbm9kZV9zdGF0X2xpc3RbW2ldXQogIAogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoImV4dHJhY3Rpbmcgbm9kZSBzdGF0cyBmb3IiLCBuYW1lcyhuZXRfc3RhdHMpW2ldLCJcbiIpCiAgZm9yIChqIGluIHNlcV9hbG9uZyhuZXRfc3RhdHNbW2ldXSkpIHsKICAgIGlmIChjbGFzcyhuZXRfc3RhdHNbW2ldXVtbal1dKSA9PSAibWljcm9OZXRQcm9wcyIpIHsKICAgICAgbm9kZV9zdGF0c1tbaV1dW1tqXV0gPC0gZXh0cmFjdF9ub2RlX3N0YXRzKG5ldF9zdGF0X2xpc3QgPSBuZXRfc3RhdHNbW2ldXVtbal1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGVzdGF0ID0gbm9kZV9wcm9wZXJ0aWVzKQogICAgICBtZXRob2QgPC0gbmFtZXMobmV0X3N0YXRzW1tpXV0pW2pdCiAgICAgIGRhdGFzZXQgPC0gbmFtZXMobmV0X3N0YXRzKVtpXQogICAgICBucm93cyA8LSBucm93KG5vZGVfc3RhdHNbW2ldXVtbal1dKQogICAgICBub2RlX3N0YXRzW1tpXV1bW2pdXSA8LSBiaW5kX2NvbHMoCiAgICAgICAgZGF0YXNldCA9IHJlcChkYXRhc2V0LG5yb3dzKSwKICAgICAgICBtZXRob2QgPSByZXAobWV0aG9kLG5yb3dzKSwKICAgICAgICBub2RlX3N0YXRzW1tpXV1bW2pdXQogICAgICAgICkKICAgIH0gZWxzZSB7CiAgICAgIGNhdCgibm8gbm9kZSBzdGF0cyB0byByZXR1cm4gZm9yIiwKICAgICAgICAgIG5hbWVzKG5ldF9zdGF0cylbaV0sCiAgICAgICAgICBuYW1lcyhub2RlX3N0YXRzW1tpXV0pW2pdLAogICAgICAgICAgIlxuIikKICAgICAgbmV4dAogICAgfQogIH0KICBub2RlX3N0YXRzW1tpXV08LWJpbmRfcm93cyhub2RlX3N0YXRzW1tpXV0pCn0Kbm9kZV9zdGF0c19kZiA8LSBiaW5kX3Jvd3Mobm9kZV9zdGF0cykKIyBwZXJmb3JtIHNvbWUgdGlkeWluZwpub2RlX3N0YXRzX2RmIDwtIG5vZGVfc3RhdHNfZGYgJT4lCiAgdGlkeXI6OnNlcGFyYXRlKGRhdGFzZXQsIGludG8gPSBjKCJTdHVkeSIsICJBY2NuX24iLCAic3VmIiksIHNlcCA9ICJfIiwgcmVtb3ZlID0gRikgJT4lIAogIGRwbHlyOjpzZWxlY3QoLUFjY25fbiwgLXN1ZikgJT4lCiAgbXV0YXRlKGxhYmVsMiA9IGlmX2Vsc2UoIXN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSxzdHJfYyhsYWJlbCwgU3R1ZHksIHNlcCA9ICJfIiksIGxhYmVsKSkKCiMgbGFiZWwyIGlzIG9ubHkgbmVjZXNzYXJ5IHdoZW4gdXNpbmcgQVNWcyBvciBPVFVzLCBub3QgaWYgdGhlcmUgaGFzIGJlZW4gdGF4b25vbWljIGFnZ2xvbWVyYXRpb24KIyBjb25zaWRlciByZW1vdmluZyB0aGUgbXV0YXRlIGluc3RydWN0aW9uCgp3cml0ZV90c3Yobm9kZV9zdGF0c19kZiwgZmlsZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9ub2Rlc3RhdHNfZGYudHh0IixzZXA9IiIpKQpybShub2RlX3N0YXRzKQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQoKYGBgCgojIyBFZGdlIHByb3BlcnRpZXMuICAKCkVkZ2VzIGFuZCBlZGdlIHByb3BlcnRpZXMgY2FuIGFsc28gYmUgZXh0cmFjdGVkIGZvciBmdXJ0aGVyIHVzZS4gSW1wb3J0YW50IGVkZ2UgcHJvcGVydGllcyBhcmU6CgoqIGVkZ2UgdHlwZSAocG9zaXRpdmUgb3IgbmVnYXRpdmUpICAKCiogZWRnZSB3ZWlnaHQgYW5kIGFzc29jaWF0aW9uIG1lYXN1cmUKCiogZWRnZSBiZXR3ZWVubmVzcyAgCgpJdCBpcyBhbHNvIGNvbnZlbmllbnQgdG8gY29tcGFyZSBlZGdlcyBhbW9uZyBkaWZmZXJlbnQgZ3JhcGhzIChhbmQgdG8gZG8gc28gaXQgbWlnaHQgYmUgY29udmVuaWVudCB0byBtYWtlIHN1cmUgdGhhdCAidG8iIGFuZCAiZnJvbSIgYXJlIGFsd2F5cyBpbiBhbHBoYWJldGljYWwgb3JkZXIsIHdoaWNoIGlzIGFsd2F5cyB0cnVlIHdpdGhpbiB0aGUgc2FtZSBncmFwaCBidXQgbWlnaHQgbm90IGJlIG5lY2Vzc2FyaWx5IHRydWUgYW1vbmcgZGlmZmVyZW50IGdyYXBocykuICAKVGhlIGZvbGxvd2luZyBjaHVuayB3aWxsIChvcHRpb25hbGx5KTogCgoqIGludGVncmF0ZSBub2RlIChjYWxjdWxhdGVkIHdpdGggYG5ldEFuYWx5c2UoKWApIGluIHRoZSB0aWR5Z3JhcGggbGlzdCwgCgoqIGNhbGN1bGF0ZSBlZGdlIGJldHdlZW5uZXNzLCAgCgoqIGNvbXBhcmUgbmV0d29ya3MgKHdpdGhpbiBkYXRhc2V0KSBieSBwcm9kdWNpbmcgYW5kIHNhdmluZyBWZW5uIGRpYWdyYW1zIG9mIHRoZSBlZGdlcwoKRmluYWxseSwgYSBkYXRhIGZyYW1lIHdpdGggYWxsIGVkZ2VzIHdpbGwgYmUgcHJvZHVjZWQgZm9yIGZ1cnRoZXIgdXNlLiAgCgoKYGBge3IgZnVydGhlcl9lZGdlX25vZGVfcHJvcGVydGllcywgZHBpID0gOTZ9CgppZihrZWVwX3RpbWUpIHRpYygiTGlzdCB3aXRoIHRpZHlncmFwaCBvYmplY3RzIGNyZWF0ZWQiKQoKIyBjcmVhdGluZyBhIHRpZHlncmFwaCBvYmplY3QgZm9yIGVhY2ggbmV0CnRpZHlncmFwaF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChNQU5faW5mX3Jlc3VsdHMpKQoKZm9yKGkgaW4gc2VxX2Fsb25nKE1BTl9pbmZfcmVzdWx0cykpewogIGlmKHZlcmJvc2Vfb3V0cHV0KSBjYXQoIkNvbnZlcnRpbmcgaW4gdGlkeWdyYXBocyBmb3IiLCBuYW1lcyhNQU5faW5mX3Jlc3VsdHMpW2ldLCJcbiIpCiAgIyB0aGUgd2FybmluZywgaWYgYW55LCBpcyBub3QgdmVyeSBpbmZvcm1hdGl2ZSwgc2hvdWxkIGNvbnNpZGVyIHBhc3NpbmcgbmFtZXMgb2YgZGF0YXNldHMgYW5kIG1ldGhvZHMKICB0aWR5Z3JhcGhfbGlzdFtbaV1dIDwtIE1BTl9pbmZfcmVzdWx0c1tbaV1dICU+JSBtYXAobWljcm9OZXRfdG9fdGlkeWdyYXBoLCBmYWlsX3dfZXJyID0gRiwgdXNlX2Fzc29fbWF0cml4ID0gVCkKfQpuYW1lcyh0aWR5Z3JhcGhfbGlzdCk8LW5hbWVzKE1BTl9pbmZfcmVzdWx0cykKaWYoa2VlcF90aW1lKSB0b2MoKQoKIyBvcHRpb25hbGx5IG1lcmdlIGZ1cnRoZXIgbm9kZSBzdGF0cyAoZGVwZW5kcyBvbiBtZXJnZV9uX3N0YXRzKSAKIyBhbmQgY2FsY3VsYXRlIGVkZ2UgYmV0d2Vlbm5lc3MgKGRlcGVuZHMgb24gY2FsY19lX2JldHcpCiMgSSBhbSB1c2luZyBhIGxvb3AKaWYoa2VlcF90aW1lKSB0aWMoIlN0YXRzIGFkZGVkIHRvIHRpZHlncmFwaHMsIGVkZ2UgZGF0YWZyYW1lIGNyZWF0ZWQiKQp0aWR5Z3JhcGhfbGlzdF93c3RhdHMgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0KSkKIyB0aGUgbGlzdCB3aXRoIHRoZSBlZGdlIGRhdGEgZnJhbWVzCmVkZ2VfbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgodGlkeWdyYXBoX2xpc3QpKQoKZm9yKGkgaW4gc2VxX2Fsb25nKHRpZHlncmFwaF9saXN0X3dzdGF0cykpewogICMgbmVlZCB0byBiZSByZXNldAogIGlubmVyX3RnbCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGggPSBsZW5ndGgodGlkeWdyYXBoX2xpc3RbW2ldXSkpCiAgaW5uZXJfZWwgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0W1tpXV0pKQogIGR0c3QgPC0gbmFtZXModGlkeWdyYXBoX2xpc3QpW2ldCiAgZm9yKGogaW4gc2VxX2Fsb25nKGlubmVyX3RnbCkpewogICAgaWYoaXMudGJsX2dyYXBoKHRpZHlncmFwaF9saXN0W1tpXV1bW2pdXSkpewogICAgbXRoZCA9IG5hbWVzKHRpZHlncmFwaF9saXN0W1tpXV0pW2pdCiAgICBuc3RhdHMgPC0gbm9kZV9zdGF0c19kZiAlPiUgZHBseXI6OmZpbHRlcihkYXRhc2V0ID09IGR0c3QgJiBtZXRob2QgPT0gbXRoZCkKICAgIGlubmVyX3RnbFtbal1dIDwtIG1lcmdlX3N0YXRzKHRnID0gdGlkeWdyYXBoX2xpc3RbW2ldXVtbal1dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vZGVfc3RhdHMgPSBuc3RhdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGViZXR3ID0gY2FsY19lX2JldHcpCiAgICAgIyBleHRyYWN0IGVkZ2UgdGliYmxlCiAgICBpbm5lcl9lbFtbal1dIDwtIGlubmVyX3RnbFtbal1dICU+JSAKICAgICAgYWN0aXZhdGUoZWRnZXMpICU+JSAKICAgICAgYXNfdGliYmxlKCkgJT4lCiAgICAgIG11dGF0ZShtZXRob2QgPSBtdGhkKQogICAgIyBkbyBuYW1pbmcKICAgIG5hbWVzKGlubmVyX3RnbClbal0gPC0gbmFtZXMoaW5uZXJfZWwpW2pdIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0W1tpXV0pW2pdCiAgICB9IGVsc2UgewogICAgICBuZXh0CiAgICB9CiAgfQogIHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dIDwtIGlubmVyX3RnbAogIGVkZ2VfbGlzdFtbaV1dIDwtIGJpbmRfcm93cyhpbm5lcl9lbCkKICBlZGdlX2xpc3RbW2ldXSA8LSBlZGdlX2xpc3RbW2ldXSAlPiUgCiAgICBtdXRhdGUoZGF0YXNldCA9IGR0c3QpICU+JSBkcGx5cjo6c2VsZWN0KGRhdGFzZXQsIG1ldGhvZCwgIShkYXRhc2V0Om1ldGhvZCkpCiAgbmFtZXModGlkeWdyYXBoX2xpc3Rfd3N0YXRzKVtpXSA8LSBuYW1lcyhlZGdlX2xpc3QpW2ldIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0KVtpXQp9CnJtKG5zdGF0cykKIyBidWlsZCBhbmQgZml4IHRoZSBlZGdlIGRmCmVkZ2VfbGlzdF9kZiA8LSBiaW5kX3Jvd3MoZWRnZV9saXN0KQoKZWRnZV9saXN0X2RmIDwtIGVkZ2VfbGlzdF9kZiAlPiUgCiAgbXV0YXRlKG5hbWVfZnJvbV9zb3J0ZWQgPSBpZl9lbHNlKGZyb21fbmFtZSA8IHRvX25hbWUsIGZyb21fbmFtZSwgdG9fbmFtZSksCiAgICAgICAgIG5hbWVfdG9fc29ydGVkID0gaWZfZWxzZShmcm9tX25hbWUgPCB0b19uYW1lLCB0b19uYW1lLCBmcm9tX25hbWUpKSAlPiUKICBtdXRhdGUoZWRnZV9uYW1lID0gc3RyX2MobmFtZV9mcm9tX3NvcnRlZCwgbmFtZV90b19zb3J0ZWQsIHNlcCA9ICItLSIpKQp3cml0ZV90c3YoZWRnZV9saXN0X2RmLCBmaWxlID0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX2VkZ2VsaXN0X2RmLnR4dCIsc2VwPSIiKSkKaWYoa2VlcF90aW1lKSB0b2MoKQoKIyBtYWtlIFZlbm4gcGxvdHMKCiMgSSBhbSB1c2luZyBhIGxvb3AKaWYoZG9fVmVubil7CiAgaWYoa2VlcF90aW1lKSB0aWMoIlZlbm4gcGxvdHMgY3JlYXRlZCBhbmQgc2F2ZWQiKQogIFZlbm5fbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBsZW5ndGgodGlkeWdyYXBoX2xpc3QpKQogIGZvcihpIGluIHNlcV9hbG9uZyh0aWR5Z3JhcGhfbGlzdCkpewogICAgIyBuZWVkIHRvIHNlbGVjdCBvbmx5IGVsZW1lbnRzIG9mIGNsYXNzIHRibF9ncmFwaAogICAgdGJsZ3JwaHMgPC0gbWFwX2xnbCh0aWR5Z3JhcGhfbGlzdFtbaV1dLCBpcy50YmxfZ3JhcGgpCiAgICBuYW1lcyhWZW5uX2xpc3QpIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0KQogICAgaWYobGVuZ3RoKHRpZHlncmFwaF9saXN0W1tpXV1bdGJsZ3JwaHNdKT4xKXsKICAgICAgaW5uZXJfbGlzdCA8LSBtYXAodGlkeWdyYXBoX2xpc3RbW2ldXVt0YmxncnBoc10sIGZ1bmN0aW9uKHgpIGFzX2lkcyhFKHgpKSkKICAgICAgVmVubl90aXRsZSA8LSBuYW1lcyh0aWR5Z3JhcGhfbGlzdClbaV0KICAgICAgVmVubl9maWxlIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwiXyIsVmVubl90aXRsZSwgIl92ZW5ucy50aWZmIixzZXA9IiIpCiAgICAgIG15X2ZpbGwgPC0gKDI6NSlbMTpsZW5ndGgodGlkeWdyYXBoX2xpc3RbW2ldXVt0YmxncnBoc10pXQogICAgICBWZW5uX2xpc3RbW2ldXSA8LSB2ZW5uLmRpYWdyYW0oaW5uZXJfbGlzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBteV9maWxsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IFZlbm5fZmlsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IDAuMDUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWluID0gVmVubl90aXRsZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4uY2V4ID0gMS41LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFpbi5mb250ZmFjZSA9ICJib2xkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4uZm9udGZhbWlseSA9ICJzYW5zIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW4ucG9zID0gYygwLjUsIDEuMDUpCiAgICAgICAgKQogICAgfQogIH0KICBybShpbm5lcl9saXN0LCBWZW5uX3RpdGxlLCBWZW5uX2ZpbGUpCiAgaWYoa2VlcF90aW1lKSB0b2MoKQp9CgppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKYGBgCkxvb2tpbmcgYXQgdGhlIFZlbm5zIGl0IGNhbiBiZSBzZWVuIHRoYXQgdGhlIHNwYXJzZXN0IG5ldHdvcmtzIGFyZSBhbG1vc3QgYWx3YXlzIHByb2R1Y2VkIGJ5IFNQSUVDLUVBU0kgYW5kIHRoYXQgdGhlIG51bWJlciBvZiBlZGdlcyBzaGFyZWQgYnkgYWxsIG1ldGhvZHMgZm9yIGFueSBnaXZlbiBpcyB2ZXJ5IGxvdy4gCgojIENvbXBhcmluZyBnbG9iYWwgcHJvcGVydGllcy4KCkNvbXBhcmluZyBnbG9iYWwgbmV0d29yayBwcm9wZXJ0aWVzIG1heSBiZSBvZiBhc3Npc3RhbmNlIGluIGV2YWx1YXRpbmcgdGhlIGVmZmVjdCBvZiB0aGUgaW5mZXJlbmNlIG1ldGhvZCBvciBvZiB0aGUgdHlwZSBvZiBzdHVkeS4gQWx0aG91Z2ggTmV0Q29NaSBvZmZlcnMgdmVyeSBlZmZlY3RpdmUgdG9vbHMgdG8gY29tcGFyZSB0d28gbmV0d29ya3MsIGl0IGRvZXMgbm90IGVhc2lseSBnZW5lcmFsaXplIHRvIG1vcmUgdGhhbiAyLTMgbmV0d29ya3MuIEhlcmUsIEkgd2lsbCB1c2UgdGhlIGdsb2JhbF9hbGxfZGYgYW5kIGdsb2JhbF9sY2NfZGYgYW5kIHByb2R1Y2UgZ3JhcGhzIHVzaW5nIFBDQS4gIApfX05vdGVfXyBsaW5lIDEwNTEgc2hvdWxkIGJlIG1hbnVhbGx5IGFkYXB0ZWQgdG8gc2hvdyBhbGwgbG9hZGluZ3MgYW5kIHNjb3JlcwoKYGBge3IgY29tcGFyZV9nbG9iYWwsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiQ29tcGFyaW5nIGdsb2JhbCBwcm9wZXJ0aWVzIikKIyBjcmVhdGUgYSBsYWJlbCBhbmQgZXh0cmFjdCBtYXRyaXggZm9yIHRoZSBhbmFseXNpcwpnbG9iYWxfYWxsX2RmXzIgPC0gZ2xvYmFsX2FsbF9kZiAlPiUKICB0aWR5cjo6c2VwYXJhdGUoZGF0YXNldCwgaW50byA9IGMoInN0dWR5IiwgInR5cGUiLCAib2JqZWN0IiksIHJlbW92ZSA9IEYpICU+JQogIHNlbGVjdCgtdHlwZSwgLW9iamVjdCkgJT4lCiAgbXV0YXRlKG1ldGhvZF9icmllZiA9IGNhc2Vfd2hlbigKICAgIG1ldGhvZCA9PSAic3BpZWNlYXNpIiB+ICJzcGkiLAogICAgbWV0aG9kID09ICJzcHJpbmciIH4gInNwciIsCiAgICBtZXRob2QgPT0gImNjcmVwZSIgfiAiY2NyIiwKICAgIG1ldGhvZCA9PSAic3BhcmNjIiB+ICJzcGEiCiAgKSkgJT4lCiAgdGlkeXI6OnVuaXRlKGxhYmVsLCBzdHVkeSwgbWV0aG9kX2JyaWVmLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEYpCgoKIyBrZWVwIG9ubHkgcmVsZXZhbnQgY29sdW1ucyBhbmQgbWFrZSBhIG1hdHJpeApnbG9iYWxfYWxsX2RmX21hdCA8LSBnbG9iYWxfYWxsX2RmXzIgJT4lIAogIGRwbHlyOjpzZWxlY3QobGFiZWwsIG5Db21wMTpubmVnZWRnZSwgc2FtcGxlcywgQ2hhbzEsIFBpZWxvdV9KLCBhdmVfQkMpICU+JQogIGNvbHVtbl90b19yb3duYW1lcygibGFiZWwiKSAlPiUgYXMubWF0cml4KCkKCiMgb3B0aW9uYWxseSBvYnRhaW4gYSBzY2F0dGVycGxvdCBtYXRyaXgKaWYodmVyYm9zZV9vdXRwdXQpIGdncGFpcnMoYXMuZGF0YS5mcmFtZShnbG9iYWxfYWxsX2RmX21hdCksIHByb2dyZXNzID0gRikKCgojIGxvb2sgYXQgdGhlIG51bWJlciBvZiBjb21wb25lbnRzICh1c2luZyBjb3JyZWxhdGlvbiBtYXRyaXgpCnZhcl90b191c2UgPC0gIShjb2xuYW1lcyhnbG9iYWxfYWxsX2RmX21hdCkgJWluJSBjKCJuY29tcCIsICJ2ZXJ0Q29ubmVjdDEiLCJlZGdlQ29ubmVjdDEiLCJudGF4YSIpKQpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRbLGMoMTo1LDg6MTQsMTc6MTgpXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgoKUENBMSA8LSBwc3ljaDo6cHJpbmNpcGFsKGdsb2JhbF9hbGxfZGZfbWF0WyxjKDE6NSw4OjE0LDE3OjE4KV0sIG5mYWN0b3JzID0gMiwgcm90YXRlID0gInZhcmltYXgiKQpQQ0ExCmJpcGxvdChQQ0ExLCB4bGltLnMgPSBjKC0xLDQpLCB5bGltLnMgPSBjKC0yLDUpLCBtYWluID0gIlBDQSBiaXBsb3QsIGFsbCB2YXJpYWJsZXMiKQpmdWxsbmV0c2NvcmVzIDwtIGNiaW5kKGdsb2JhbF9hbGxfZGZfMixQQ0ExJHNjb3JlcykKIyB0aGUgc2NvcmUgcGxvdApnZ3Bsb3QoZnVsbG5ldHNjb3JlcywgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gImFsbCB2YXJpYWJsZXMiKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoInNwaWVjZWFzaSIgPSAxNiwgInNwcmluZyIgPSAxNywgImNjcmVwZSIgPSAxNSwgInNwYXJjYyIgPSAxOCkpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIodHlwZSA9ICJxdWFsIiwgcGFsZXR0ZSA9ICJQYWlyZWQiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgp2YXJfdG9fdXNlIDwtIGNvbG5hbWVzKGdsb2JhbF9hbGxfZGZfbWF0KSAlaW4lIGMoIm5jb21wMSIsICJtb2R1bGFyaXR5MSIsImRlbnNpdHkxIiwiY2x1c3RDb2VmMSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYXZQYXRoMSIsICJwZXAxIiwgIlBpZWxvdV9KIiwgImF2ZV9CQyIsICJubm9kZXMiKQpjYXQoInZhcmlhYmxlcyB0byB1c2U6ICIsIHZhcl90b191c2UsICJcbiIpCgpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRbLHZhcl90b191c2VdLCBmYSA9ICJwYyIsIG4uaXRlciA9IDEwMCkKClBDQTIgPC0gcHJpbmNpcGFsKGdsb2JhbF9hbGxfZGZfbWF0Wyx2YXJfdG9fdXNlXSwgbmZhY3RvcnMgPSAyLCByb3RhdGUgPSAidmFyaW1heCIpClBDQTIKYmlwbG90KFBDQTIpCmZ1bGxuZXRzY29yZXNfMiA8LSBjYmluZChnbG9iYWxfYWxsX2RmXzIsUENBMiRzY29yZXMpCnZhcl9hY2NfUkMxIDwtIHJvdW5kKFBDQTIkVmFjY291bnRlZFs0LDFdLDMpCnZhcl9hY2NfUkMyIDwtIHJvdW5kKFBDQTIkVmFjY291bnRlZFs0LDJdLDMpCiMgdGhlIHNjb3JlIHBsb3QKZ2dwbG90KGZ1bGxuZXRzY29yZXNfMiwgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gInNlbGVjdGVkIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICBsYWJzKHggPSBwYXN0ZSgiUkMxICgiLCB2YXJfYWNjX1JDMSwgIikiLCBzZXAgPSIiKSwKICAgICAgIHggPSBwYXN0ZSgiUkMyICgiLCB2YXJfYWNjX1JDMiwgIikiLCBzZXAgPSIiKSkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKCmdnc2F2ZShmaWxlbmFtZSA9IHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9QQ0EudGlmZiIsc2VwPSIiKSwgZGV2aWNlID0gInRpZmYiLCB3aWR0aCA9IDYsIAogICAgICAgaGVpZ2h0ID0gNSwgdW5pdHMgPSAiaW4iLCBkcGk9NjAwKQoKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCldpdGggdGhlc2UgbWFueSBkYXRhc2V0cyBhbmQgaW5mZXJlbmNlIG1ldGhvZHMgdGhlIFBDQSBiZWNvbWVzIGRpZmZpY3VsdCB0byB1c2UuIE5vdGUgaG93IHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSBuZXR3b3JrIGFyZSwgYXMgZXhwZWN0ZWQsIHNwZWNpZmljIHRvIGFueSBnaXZlbiBpbmZlcmVuY2UgbWV0aG9kcyBhbmQgZGF0YS1zZXQgYW5kIGhvdyBtZXRob2RzIGJhc2VkIG9uIGNvcnJlbGF0aW9uIChzcGFyQ0MgYW5kIENDUkVQRSkgdGVuZCB0byBiZSBjbG9zZXIgYW1vbmcgdGhlbSB0aGFuIHRvIG1ldGhvZHMgYmFzZWQgb24gY29uZGl0aW9uYWwgZGVwZW5kZW5jZS4gCgpUaGUgYW5hbHlzaXMgaXMgcmVwZWF0ZWQgYmVsb3cgb24gdGhlIGxhcmdlc3QgY29ubmVjdGVkIGNvbXBvbmVudCBmb3IgZWFjaCBuZXR3b3JrLiAgCgpgYGB7ciBjb21wYXJlX2dsb2JhbF9MQ0MsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiQ29tcGFyaW5nIGdsb2JhbCBwcm9wZXJ0aWVzIG9uIExDQyIpCiMgY3JlYXRlIGEgbGFiZWwgYW5kIGV4dHJhY3QgbWF0cml4IGZvciB0aGUgYW5hbHlzaXMKZ2xvYmFsX2FsbF9kZl8zIDwtIGdsb2JhbF9sY2NfZGYgJT4lCiAgdGlkeXI6OnNlcGFyYXRlKGRhdGFzZXQsIGludG8gPSBjKCJzdHVkeSIsICJ0eXBlIiwgIm9iamVjdCIpLCByZW1vdmUgPSBGKSAlPiUKICBzZWxlY3QoLXR5cGUsIC1vYmplY3QpICU+JQogIG11dGF0ZShtZXRob2RfYnJpZWYgPSBjYXNlX3doZW4oCiAgICBtZXRob2QgPT0gInNwaWVjZWFzaSIgfiAic3BpIiwKICAgIG1ldGhvZCA9PSAic3ByaW5nIiB+ICJzcHIiLAogICAgbWV0aG9kID09ICJjY3JlcGUiIH4gImNjciIsCiAgICBtZXRob2QgPT0gInNwYXJjYyIgfiAic3BhIgogICkpICU+JQogIHRpZHlyOjp1bml0ZShsYWJlbCwgc3R1ZHksIG9ial90eXBlLCBtZXRob2RfYnJpZWYsIHNlcCA9ICJfIiwgcmVtb3ZlID0gRikKCgojIGtlZXAgb25seSByZWxldmFudCBjb2x1bW5zIGFuZCBtYWtlIG1hdHJpeApnbG9iYWxfYWxsX2RmX21hdF8yIDwtIGdsb2JhbF9hbGxfZGZfMyAlPiUgCiAgZHBseXI6OnNlbGVjdChsYWJlbCwgbGNjU2l6ZTE6bm5lZ2VkZ2UsIHNhbXBsZXMsIENoYW8xLCBQaWVsb3VfSiwgYXZlX0JDKSAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXMoImxhYmVsIikgJT4lIGFzLm1hdHJpeCgpCiMgbmVlZCB0byByZW1vdmUgYW4gSW5mIHZhbHVlLCBJIGFtIHJlbW92aW5nIHRoZSByb3cKCiMgb2J0YWluIGEgc2NhdHRlcnBsb3QgbWF0cml4CmlmKHZlcmJvc2Vfb3V0cHV0KSBnZ3BhaXJzKGFzLmRhdGEuZnJhbWUoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyxdKSwgcHJvZ3Jlc3MgPSBGKQoKCiMgbG9vayBhdCB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMgKHVzaW5nIGNvcnJlbGF0aW9uIG1hdHJpeCk6IG11c3QgZXhjbHVkZQojIGF2ZSBwYXRoIGxlbmd0aCBhbmQgY2x1c3RlcmluZyBjb2VmZmljaWVudCB3aGljaCBjb250YWluIEluZiBhbmQgTmFOCgpwc3ljaDo6ZmEucGFyYWxsZWwoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyxjKDE6Myw2LDk6MTQsMTc6MTgpXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgoKUENBMV9sY2MgPC0gcHN5Y2g6OnByaW5jaXBhbChnbG9iYWxfYWxsX2RmX21hdF8yWy0zLGMoMTozLDYsOToxNCwxNzoxOCldLCBuZmFjdG9ycyA9IDIsIHJvdGF0ZSA9ICJ2YXJpbWF4IikKUENBMV9sY2MKYmlwbG90KFBDQTFfbGNjLCBtYWluID0gIlBDQSBiaXBsb3QsIGFsbCB2YXJpYWJsZXMiKQpmdWxsbmV0c2NvcmVzX2xjYyA8LSBjYmluZChnbG9iYWxfYWxsX2RmXzNbLTMsXSxQQ0ExX2xjYyRzY29yZXMpCiMgdGhlIHNjb3JlIHBsb3QKZ2dwbG90KGZ1bGxuZXRzY29yZXNfbGNjLCBtYXBwaW5nID0gYWVzKHggPSBSQzEsIHkgPSBSQzIsIHNoYXBlID0gbWV0aG9kLCBjb2xvdXIgPSBzdHVkeSkpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAiYWxsIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKCnZhcl90b191c2VfbGNjIDwtIGNvbG5hbWVzKGdsb2JhbF9hbGxfZGZfbWF0KSAlaW4lIGMoImxjY1NpemUxIiwgIm1vZHVsYXJpdHkxIiwiZGVuc2l0eTEiLCAicGVwMSIsICJQaWVsb3VfSiIsICJhdmVfQkMiLCAibm5vZGVzIikKY2F0KCJ2YXJpYWJsZXMgdG8gdXNlOiAiLCB2YXJfdG9fdXNlX2xjYywgIlxuIikKCnBzeWNoOjpmYS5wYXJhbGxlbChnbG9iYWxfYWxsX2RmX21hdF8yWy0zLHZhcl90b191c2VfbGNjXSwgZmEgPSAicGMiLCBuLml0ZXIgPSAxMDApCgpQQ0EyX2xjYyA8LSBwcmluY2lwYWwoZ2xvYmFsX2FsbF9kZl9tYXRfMlstMyx2YXJfdG9fdXNlX2xjY10sIG5mYWN0b3JzID0gMiwgcm90YXRlID0gInZhcmltYXgiKQpQQ0EyX2xjYwpiaXBsb3QoUENBMl9sY2MpCmZ1bGxuZXRzY29yZXNfMl9sY2MgPC0gY2JpbmQoZ2xvYmFsX2FsbF9kZl8yWy0zLF0sUENBMl9sY2Mkc2NvcmVzKQojIHRoZSBzY29yZSBwbG90CnZhcl9hY2NfUkMxX2xjYyA8LSByb3VuZChQQ0EyJFZhY2NvdW50ZWRbNCwxXSwzKQp2YXJfYWNjX1JDMl9sY2MgPC0gcm91bmQoUENBMiRWYWNjb3VudGVkWzQsMl0sMykKIyB0aGUgc2NvcmUgcGxvdApnZ3Bsb3QoZnVsbG5ldHNjb3Jlc18yX2xjYywgbWFwcGluZyA9IGFlcyh4ID0gUkMxLCB5ID0gUkMyLCBzaGFwZSA9IG1ldGhvZCwgY29sb3VyID0gc3R1ZHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gInNlbGVjdGVkIHZhcmlhYmxlcyIpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygic3BpZWNlYXNpIiA9IDE2LCAic3ByaW5nIiA9IDE3LCAiY2NyZXBlIiA9IDE1LCAic3BhcmNjIiA9IDE4KSkgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gIlBhaXJlZCIpICsKICBsYWJzKHggPSBwYXN0ZSgiUkMxICgiLCB2YXJfYWNjX1JDMV9sY2MsICIpIiwgc2VwID0iIiksCiAgICAgICB4ID0gcGFzdGUoIlJDMiAoIiwgdmFyX2FjY19SQzJfbGNjLCAiKSIsIHNlcCA9IiIpKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpCgpnZ3NhdmUoZmlsZW5hbWUgPSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfUENBX2xjYy50aWZmIixzZXA9IiIpLCBkZXZpY2UgPSAidGlmZiIsIHdpZHRoID0gNiwgCiAgICAgICBoZWlnaHQgPSA1LCB1bml0cyA9ICJpbiIsIGRwaT02MDApCgppZihrZWVwX3RpbWUpIHRvYygpCmBgYAoKVGhlIHJlc3VsdHMgYXJlIHNpbWlsYXIsIGFuZCB0aGVyZSBpcyBub3QgbXVjaCB0byBhZGQuIFdoaWxlIFNQSUVDLUVBU0kgcHJvZHVjZXMgdGhlIG1vc3Qgc3BhcnNlIG5ldHdvcmtzIChhbmQgY2FuIGJlIGEgY2FuZGlkYXRlIGZvciB0aGUgZGV0ZWN0aW9uIG9mIHRoZSBtb3N0IHBhcnNpbW9uaW91cyBpbnRlcmFjdGlvbiBzZXRzKSwgQ0NSRVBFIGFuZCBTcGFyQ0MgYm90aCBtYXkgZGV0ZWN0IGluZGlyZWN0IGludGVyYWN0aW9ucyBhbmQsIGRldGVjdGluZyBjbHVzdGVycyBpbiB0aGlzIG5ldHdvcmtzIG1heSBiZSB1c2VmdWwgdG8gZGV0ZWN0IHN1Yi1iaW9tZXMgd2l0aGluIGEgZ2l2ZW4gc3R1ZHkuCgogCiMgUGxvdHRpbmcgc2VsZWN0ZWQgbmV0d29ya3MuICAKCkkgd2lsbCBub3cgdXNlIHRpZHlncmFwaCBhbmQgZ2dyYXBoIHRvIHBsb3QgdGhlIG5ldHdvcmtzLiBUaGUgdGhpY2tuZXNzIG9mIHRoZSBlZGdlcyBpcyBtYWRlIHByb3BvcnRpb25hbCB0byB0aGUgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIGFzc29jaWF0aW9uIG1lYXN1cmUuIENvcHJlc2VuY2UgZWRnZXMgYXJlIGluIGdyZWVuLCBtdXR1YWwgZXhjbHVzaW9uIG9uZXMgaW4gcmVkLiBTaXplIG9mIHRoZSBub2RlcyBpcyBtYWRlIHByb3BvcnRpb25hbCB0byB0aGUgdG90YWwgZGVncmVlIChuZWdhdGl2ZSBkZWdyZWUgKyBwb3NpdGl2ZSBkZWdyZWUpLiBUaGUgY29sb3Igb2YgdGhlIG5vZGVzIGlzIGRldGVybWluZWQgYnkgdGhlIHBoeWx1bS4gU29tZSBvZiB0aGlzIG9wdGlvbnMgY2FuIGJlIGFkanVzdGVkIGluIHRoZSBjYWxsIHRvIHRoZSBwbG90X2dncmFwaCgpIGZ1bmN0aW9uIGJlbG93LiBPdGhlcnMgbXVzdCBiZSBhZGp1c3RlZCBpbiB0aGUgY29kZSBvZiB0aGUgZnVuY3Rpb24gKGFsbCBmdW5jdGlvbnMgYXJlIGluIHRoZSBzb3VyY2UgZm9sZGVyKS4gIAoKYGBge3IgcGxvdHRpbmdfbmV0d29ya3MsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiUGxvdHRpbmcgdGhlIG5ldHdvcmtzIHdpdGggZ2dyYXBoIikKIyBjcmVhdGUgYSBsaXN0IG9mIG5ldHdvcmsgcGxvdHMKbmV0cGxvdF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aCh0aWR5Z3JhcGhfbGlzdF93c3RhdHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcodGlkeWdyYXBoX2xpc3Rfd3N0YXRzKSl7CiAgbmV0cGxvdF9saXN0XzIgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbGVuZ3RoKHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dKSkKICBkdHN0IDwtIG5hbWVzKHRpZHlncmFwaF9saXN0X3dzdGF0cylbaV0KICBmb3IoaiBpbiBzZXFfYWxvbmcodGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV0pKXsKICAgIGlmKCFpcy50YmxfZ3JhcGgodGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV1bW2pdXSkpewogICAgICBuZXRwbG90X2xpc3RfMltbal1dIDwtICJubyBwbG90IHRvIHJldHVybiIKICAgICAgbmV4dAogICAgfSBlbHNlIHsKICAgICAgdGcgPC0gdGlkeWdyYXBoX2xpc3Rfd3N0YXRzW1tpXV1bW2pdXQogICAgICBtdGhkIDwtIG5hbWVzKHRpZHlncmFwaF9saXN0X3dzdGF0c1tbaV1dKVtqXQogICAgICAjIHRoZSBkZWZhdWx0IGZvciB0aGUgYXJndW1lbnQgYzBsMHIgaXMgcGh5bHVtLCBvdGhlcndpc2UKICAgICAgIyB1c2UgYzBsMHIgPSBjbHVzdF9tZW1iCiAgICAgICMgYXJndW1lbnQgbHAgPSAiYm90dG9tIiBpcyB0aGUgZGVmYXVsdDsgInJpZ2h0IiBpcyBhbiBhbHRlcm5hdGl2ZQogICAgICBuZXRwbG90X2xpc3RfMltbal1dIDwtIHBsb3RfZ2dyYXBoKHRpZHlfZ3JhcGggPSB0ZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBtdGhkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSBkdHN0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxwID0gInJpZ2h0IikKICAgICAgbmFtZXMobmV0cGxvdF9saXN0XzIpW2pdIDwtIG10aGQKICAgICAgcHJpbnQobmV0cGxvdF9saXN0XzJbW2pdXSkKICAgIH0KICB9CiAgbmV0cGxvdF9saXN0W1tpXV0gPC0gbmV0cGxvdF9saXN0XzIKICBuYW1lcyhuZXRwbG90X2xpc3QpW2ldIDwtIGR0c3QKfQoKcm0odGcsIGR0c3QsIG10aGQsIG5ldHBsb3RfbGlzdF8yKQppZihwbGF5X2F1ZGlvKSBiZWVwKHNvdW5kID0gNikKaWYoa2VlcF90aW1lKSB0b2MoKQpgYGAKCkR1ZSB0byB0aGUgbGFyZ2UgbnVtYmVyIG9mIGludGVyYWN0aW9ucyBpdCBpcyBoYXJkIHRvIGRpc2N1c3MgdGhlIHJlc3VsdHMuIEluIHNvbWUgb2YgdGhlIGdyYXBocyBjbHVzdGVycyBhcmUgY2xlYXJseSBldmlkZW50IChhbmQgY291bGQgYmUgaGlnaGxpZ2h0ZWQgYnkgdXNpbmcgdGhlIGNsdXN0ZXIgaWQgZm9yIGNvbG9ycykuIFRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGdyYXBocyBvYnRhaW5lZCB3aXRoIHRoZSBkaWZmZXJlbnQgbWV0aG9kcyBmb3IgZWFjaCBnaXZlbiBkYXRhc2V0IGlzIGFsc28gZGlmZmVyZW50LiBUaGlzIG1heSBiZSBkdWUgdG8gdGhlIGZhY3QgdGhhdCBib3RoIHRoZSBjb3JyZWxhdGlvbiBiYXNlZCBtZXRob2RzIG1heSBkZXRlY3QgaW5kaXJlY3QgaW50ZXJhY3Rpb25zLiAgCkluIGFkZGl0aW9uLCBnaXZlbiB0aGUgc2NvcGUgb2YgdGhpcyBhbmFseXNpcyAod2l0aCBlbXBoYXNpcyBvbiBub2RlcyBhbmQgZWRnZXMgY29uc2VydmVkIGFjcm9zcyBkaWZmZXJlbnQgc3R1ZGllcykgdGhlcmUgaXMgbGl0dGxlIHBvaW50IGluIGRpc2N1c3NpbmcgaW5kaXZpZHVhbCBuZXR3b3JrcywgYmVjYXVzZSB0aGlzIHdvdWxkIHJlcXVpcmUgZ29pbmcgaW50byBkZXRhaWwgaW4gdGhlIGV4cGVyaW1lbnRhbCBhcHByb2FjaCB1c2VkIGluIGVhY2ggc3R1ZHkuICAKVGhlIGZvbGxvd2luZyBjaHVuayBpcyBkZXNpZ25lZCB0byBjb21wYXJlIG5ldHdvcmsgcGxvdHMgYnVpbHQgd2l0aCBkaWZmZXJlbnQgbWV0aG9kcyBpbiBhIGdyaWQuIFR3byBvZiB0aGUgZGF0YXNldHMgKGNob3NlbiBiZWNhdXNlIHRoZXkgcmV0dXJuZWQgYSBuZXR3b3JrIGZvciBhbGwgaW5mZXJlbmNlIG1ldGhvZHMgYW5kIGJlY2F1c2UgdGhleSBoYWQgbmV0d29ya3Mgb2YgZGlmZmVyZW50IGNvbXBsZXhpdHkgaGF2ZSBiZWVuIGNob3NlbiBoZXJlKS4gSG93ZXZlciwgZHVlIHRvIHRoZSBsYXJnZSBudW1iZXIgb2YgZWxlbWVudHMgaW4gdGhlIGdyYXBoLCBwbG90dGluZyBhbGwgdGhlIGVsZW1lbnRzIChncmFwaCwgbGVnZW5kLCB0aXRsZSkgaXMgZGlmZmljdWx0IGFuZCBtYXkgcmVxdWlyZSBhY3Rpbmcgb24gZ3JhcGhpYyBwYXJhbWV0ZXJzLiAgCgpgYGB7ciBjb21wYXJlX25ldHdvcmtfcGxvdHMsIGRwaSA9IDk2LCBldmFsID0gRn0KaWYoa2VlcF90aW1lKSB0aWMoIlBsb3R0aW5nIGFuZCBzYXZpbmcgbmV0d29ya3MgaW4gYSBncmlkIikKCnAxPC1uZXRwbG90X2xpc3RbWzJdXVtbMV1dCnAyPC1uZXRwbG90X2xpc3RbWzJdXVtbMl1dCnAzPC1uZXRwbG90X2xpc3RbWzJdXVtbM11dCnA0PC1uZXRwbG90X2xpc3RbWzJdXVtbNF1dCmdncmlkXzEgPC0gZWdnOjpnZ2FycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5yb3cgPSAyLCBuY29sID0gMikKZm4gPC0gcGFzdGUoZmlsZS5wYXRoKG91dHB1dF9mb2xkZXIsb3V0X2ZpbGVuYW1lX3ByZWYpLCAiX1NUMTEwLnRpZmYiLHNlcD0iIikKZ2dzYXZlKGdncmlkXzEsIGZpbGVuYW1lID0gZm4sIGRwaSA9IDYwMCwgd2lkdGggPSA3LCBoZWlnaHQgPSA3KQpnZ3JpZF8xCgpwNTwtbmV0cGxvdF9saXN0W1s5XV1bWzFdXQpwNjwtbmV0cGxvdF9saXN0W1s5XV1bWzJdXQpwNzwtbmV0cGxvdF9saXN0W1s5XV1bWzNdXQpwODwtbmV0cGxvdF9saXN0W1s5XV1bWzRdXQpnZ3JpZF8yIDwtIGVnZzo6Z2dhcnJhbmdlKHA1LCBwNiwgcDcsIHA4LCBucm93ID0gMiwgbmNvbCA9IDIpCmZuIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9TVDEzNi50aWZmIixzZXA9IiIpCmdnc2F2ZShnZ3JpZF8yLCBmaWxlbmFtZSA9IGZuLCBkcGkgPSA2MDAsIHdpZHRoID0gNywgaGVpZ2h0ID0gNykKZ2dyaWRfMgoKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKcm0ocDEsIHAyLCBwMywgcDQsIHA1LCBwNiwgcDcsIHA4KQpgYGAKCgojIE5vZGUgYW5hbHlzaXMuCgpXaGVuIGNvbXBhcmluZyBuZXR3b3JrcyBmcm9tIHNldmVyYWwgc3R1ZGllcyBpdCBtaWdodCBiZSBvZiBpbnRlcmVzdCB0byBjbGFzc2lmeSBub2RlcyBvbiB0aGUgYmFzaXMgb2YgdGhlaXIgbWVhc3VyZXMgb2YgY2VudHJhbGl0eSBhY3Jvc3MgbXVsdGlwbGUgc3R1ZGllczogaXMgdGhlcmUgYSBzdXBlciBodWIgKGkuZS4gYSBub2RlIHdoaWNoIGkgYSBodWIgaW4gc2V2ZXJhbCBuZXR3b3Jrcyk/IEhvdyBhcmUgZGlmZmVyZW50IGNlbnRyYWxpdHkgbWVhc3VyZXMgcmVsYXRlZD8gQXJlIHRoZXJlIG5vZGVzIHdoaWNoIGVuZ2FnZSBtb3JlIG9mdGVuIGluIG5lZ2F0aXZlIGludGVyYWN0aW9ucz8gT2YgY291cnNlIGl0IHdvdWxkIGJlIHBvaW50bGVzcyB0byBjb21wYXJlIGFjcm9zcyBzZXZlcmFsIG1ldGhvZHMgb2YgaW5mZXJlbmNlIG9yLCBnaXZlbiB0aGUgcmVzdWx0cyBhYm92ZSwgYmV0d2VlbiBBU1YgYW5kIEZNQk4gc3R1ZGllcyAod2hlbiBhZ2dyZWdhdGlvbiBhdCB0aGUgZ2VudXMgbGV2ZWwgaXMgdXNlZCkuClRoaXMgc2NyaXB0IHdpbGwgcHJvZHVjZSBhIGZldyBub2RlIHBsb3RzIGFzIGEgcHJvb2Ygb2YgY29uY2VwdC4gQm90aCBTUElFQy1FQVNJIGFuZCBTcGFyQ0Mgd2lsbCBiZSB1c2VkIG9uIGRhdGEgZXh0cmFjdGVkIGZyb20gRk1CTiBhbmQgYWdncmVnYXRlZCBhdCB0aGUgZ2VudXMgbGV2ZWwuCgoKCmBgYHtyIG5vZGVfYW5hbHlzaXNfaHViX2ZyZXEsIGRwaSA9IDk2fQppZihrZWVwX3RpbWUpIHRpYygiTm9kZSBhbmFseXNpcyIpCgpteW1ldGhvZHMgPC0gYygic3BpZWNlYXNpIiwic3BhcmNjIikKbl90b19sYWJlbCA8LSAyMCAjIG1heCBudW1iZXIgb2Ygbm9kZXMgdG8gbGFiZWwgaW4gdGhlIG5vZGUgcGxvdHMKbm9kZV9hbmFseXNpc19saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChteW1ldGhvZHMpKQpmb3IoaSBpbiBzZXFfYWxvbmcobXltZXRob2RzKSl7CiAgIyBjcmVhdGUgdGhlIGRmcywgb25lIGZvciBzcGllYy1lYXNpIGFuZCBvbmUgZm9yIHNwYXJDQyBhbmQgb25seSBzZWxlY3QgRk1CTgogICMgb25seSBzZWxlY3Qgbm9kZXMgd2l0aCBkZWdyZWU+MAogIEZNQk5fbm9kZV9kZiA8LSBub2RlX3N0YXRzX2RmICU+JQogICAgZHBseXI6OmZpbHRlcihtZXRob2QgPT0gbXltZXRob2RzW2ldKSAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoZGVncmVlPjApICU+JQogICAgbXV0YXRlKFN0dWR5ID0gYXMuZmFjdG9yKFN0dWR5KSwKICAgICAgICAgICByZWxfcG9zX2RlZ3JlZSA9IHBvc19kZWdyZWUgLyBzdW0ocG9zX2RlZ3JlZSkpICU+JQogICAgZHBseXI6OnJlbmFtZSh0bGFiZWwgPSBsYWJlbCkKICAKICAKICAjIHVzZSBhIGxvb3AgdG8gZG8gdGhlIG5vZGUgZGVncmVlIGdyYXBocywgcHJpbnQgdGhlbSBhbmQgcHV0IHRoZW0gaW4gYSBsaXN0CiAgbm9kZV9kZWdyZWVfcGxvdF9saXN0IDwtIHZlY3RvcigibGlzdCIsIGxlbmd0aD1ubGV2ZWxzKEZNQk5fbm9kZV9kZiRzdHVkeSkpCiAgCiAgZm9yKGogaW4gc2VxX2Fsb25nKGxldmVscyhGTUJOX25vZGVfZGYkU3R1ZHkpKSl7CiAgICBndGl0bGUgPSBwYXN0ZShsZXZlbHMoRk1CTl9ub2RlX2RmJFN0dWR5KVtqXSwgbXltZXRob2RzW2ldLCBzZXAgPSAiLCAiKQogICAgdGVtcF9kZiA8LSBGTUJOX25vZGVfZGYgJT4lIAogICAgICBkcGx5cjo6ZmlsdGVyKFN0dWR5ID09bGV2ZWxzKEZNQk5fbm9kZV9kZiRTdHVkeSlbal0pICU+JQogICAgICBtdXRhdGUoZGdyID0gcG9zX2RlZ3JlZSArIG5lZ19kZWdyZWUpICU+JQogICAgICBhcnJhbmdlKC1kZ3IpICU+JQogICAgICByb3dpZF90b19jb2x1bW4oKSAlPiUgCiAgICAgIG11dGF0ZSh0b19sYWJlbCA9IGlmX2Vsc2UoKHJvd2lkPD0yMCB8IGlzX2h1YiksIHRsYWJlbCwgTkFfY2hhcmFjdGVyXykpCiAgICAKICAgIGF2ZV9kZWdyZWUgPSBtZWFuKHRlbXBfZGYkZGdyLCBuYS5ybSA9IFQpCiAgICBhdmVfcG9zX2RlZ3JlZSA9IG1lYW4odGVtcF9kZiRwb3NfZGVncmVlLCBuYS5ybSA9IFQpCiAgICAjIGNyZWF0aW5nIHRoZSBwbG90LCBvbmx5IHRoZSBuYW1lcyBvZiB0aGUgdG9wIDIwIG5vZGVzIChpbiB0ZXJtcyBvZiBkZWdyZWUpCiAgICAjIGFyZSBwbG90dGVkLCBodWJzIGFyZSBhbHdheXMgcGxvdHRlZAogICAgZ2dwIDwtIGdncGxvdCgKICAgICAgdGVtcF9kZiwKICAgICAgbWFwcGluZyA9IGFlcygKICAgICAgICB4ID0gcG9zX2RlZ3JlZSwKICAgICAgICB5ID0gZGdyLAogICAgICAgIGxhYmVsID0gc3RyX3RydW5jKGdlbnVzLCAxMiwgc2lkZSA9ICJjZW50ZXIiKQogICAgICApCiAgICApICsKICAgICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbGluZXR5cGUgPSAzLCBzZSA9IEYsIGNvbG9yID0gSSgiYmxhY2siKSwgc2hvdy5sZWdlbmQgPSBGKSArCiAgICAgIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvciA9IHBoeWx1bSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSByZWxBYnVuZGFuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IGJldHdlZW4pKSArCiAgICAgIGdlb21fdGV4dF9yZXBlbChzaG93LmxlZ2VuZCA9IEYsIG1heC5vdmVybGFwcyA9IDIwLCBhbHBoYSA9IEkoMC41KSkgKwogICAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gMSkgKwogICAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSBhdmVfZGVncmVlLCBsaW5ldHlwZSA9IDMsIHNob3cubGVnZW5kID0gRikgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhdmVfcG9zX2RlZ3JlZSwgbGluZXR5cGUgPSAzLCBzaG93LmxlZ2VuZCA9IEYpICsKICAgICAgc2NhbGVfYWxwaGFfY29udGludW91cyhyYW5nZSA9IGMoMC40LCAxKSkgKwogICAgICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsNikpICsKICAgICAgbGFicygKICAgICAgICB4ID0gInBvc2l0aXZlIGRlZ3JlZSIsCiAgICAgICAgeSA9ICJkZWdyZWUiLAogICAgICAgIHNpemUgPSAicmVsYXRpdmUgYWJ1bmRhbmNlIiwKICAgICAgICBhbHBoYSA9ICJiZXR3ZWVubmVzcyIsCiAgICAgICAgdGl0bGUgPSBndGl0bGUKICAgICAgKSArCiAgICAgIHRoZW1lX2J3KCkgKwogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKICAgIAogICAgcHJpbnQoZ2dwKQogICAgbm9kZV9kZWdyZWVfcGxvdF9saXN0W1tqXV0gPC0gZ2dwCiAgICBuYW1lcyhub2RlX2RlZ3JlZV9wbG90X2xpc3RbW2pdXSkgPC0gZ3RpdGxlCiAgfQogIGZpbGVfbmFtZSA8LSBwYXN0ZShmaWxlLnBhdGgob3V0cHV0X2ZvbGRlcixvdXRfZmlsZW5hbWVfcHJlZiksICJfIiwgbXltZXRob2RzW2ldLCAiX25vZGVwbG90cy5SZGF0YSIsc2VwPSIiKQogIHNhdmUobm9kZV9kZWdyZWVfcGxvdF9saXN0LCBmaWxlID0gZmlsZV9uYW1lKQogIAogICMgY2FsY3VsYXRlIGZyZXF1ZW5jeSBmb3IgaHVicyAobWVhbmluZ2Z1bCBvbmx5IGlmIHlvdSBoYXZlIG1hbnkgdGF4YSBhbmQgdGhlIHBlcmNlbnRpbGUgZm9yIGh1YiBkZXRlY3Rpb24gaXMgbG93LCBzYXkgMC43NS0wLjkwKQogIG5fZGF0YXNldHMgPC0gZHBseXI6Om5fZGlzdGluY3QoRk1CTl9ub2RlX2RmJGRhdGFzZXQpCiAgRk1CTl9ub2RlX2RmX2h1YnMgPC0gRk1CTl9ub2RlX2RmICU+JQogICAgZHBseXI6OmZpbHRlcihpc19odWIgPT0gVCkgJT4lCiAgICBncm91cF9ieSh0bGFiZWwsIHBoeWx1bSwgY2xhc3MpICU+JQogICAgc3VtbWFyaXNlKGh1Yl9mcmVxID0gbigpL25fZGF0YXNldHMpICU+JQogICAgYXJyYW5nZSgtaHViX2ZyZXEpCiAgY2F0KCJtZXRob2QiLCBteW1ldGhvZHNbaV0sICJodWIgZnJlcXVlbmN5IiwgIlxuIikKICBoZWFkKEZNQk5fbm9kZV9kZl9odWJzLCAyMCkKICAjIHNhdmUgZWxlbWVudHMgaW4gdGhlIGxpc3QKbm9kZV9hbmFseXNpc19saXN0W1tpXV0gPC0gbGlzdCgKICBub2RlX2RmID0gRk1CTl9ub2RlX2RmLAogIHBsb3RfbGlzdCA9IG5vZGVfZGVncmVlX3Bsb3RfbGlzdCwKICBub2RlX2RmX2h1YnMgPSBGTUJOX25vZGVfZGZfaHVicwopCm5hbWVzKG5vZGVfYW5hbHlzaXNfbGlzdClbaV08LSBteW1ldGhvZHNbaV0KICAKfQoKaWYocGxheV9hdWRpbykgYmVlcChzb3VuZCA9IDYpCmlmKGtlZXBfdGltZSkgdG9jKCkKcm0oRk1CTl9ub2RlX2RmLCBub2RlX2RlZ3JlZV9wbG90X2xpc3QsIHRlbXBfZGYsIEZNQk5fbm9kZV9kZl9odWJzKQpgYGAKClRoZSBub2RlIHBsb3RzIHByb3ZpZGUgYSBudW1iZXIgb2YgdmlzdWFsIGN1ZXM6CgoqIHRoZSBob3Jpem9udGFsIGFuZCB2ZXJ0aWNhbCBkb3R0ZWQgbGluZXMgc2hvdyB0aGUgYXZlcmFnZSB2YWx1ZSBmb3IgZGVncmVlIGFuZCBwb3NpdGl2ZSBkZWdyZWUgYW5kIG1heSBoZWxwIGluIGlkZW50aWZ5aW5nIG5vZGVzIHdob3NlIHZhbHVlcyBhcmUgZmFyIGZyb20gdGhlIG1lYW47ICAKCiogdGhlIGRpYWdvbmFsIGNvbnRpbnVvdXMgbGluZSBjb3JyZXNwb25kcyB0byBwb2ludHMgZm9yIHdoaWNoIHBvc2l0aXZlIGRlZ3JlZSBhbmQgZGVncmVlIGFyZSBlcXVhbDsgcG9pbnRzIGFib3ZlIHRoZSBsaW5lIGhhdmUgYXQgbGVhc3QgMSBuZWdhdGl2ZSBpbnRlcmFjdGlvbgoKKiB0aGUgZGlhZ29uYWwgZG90dGVkIGxpbmUgaXMgdGhlIGxpbmVhciByZWdyZXNzaW9uIGxpbmUgZm9yIGRlZ3JlZSBhcyBhIGZ1bmN0aW9uIG9mIHBvc2l0aXZlIGRlZ3JlZTsgaXRzIHBvc2l0aW9uIGFuZCBzbG9wZSByZWxhdGl2ZSB0byB0aGUgcHJldmlvdXMgbGluZSBpbmRpY2F0ZXMgb24gYXZlcmFnZSBob3cgbXVjaCB0aGUgcmF0aW8gYmV0d2VlbiB0b3RhbCBkZWdyZWUgYW5kIHBvc2l0aXZlIGRlZ3JlZSBjaGFuZ2VzIGFzIGEgZnVuY3Rpb24gb2YgZGVncmVlOyBkYXRhIHBvaW50cyB3aGljaCBhcmUgZmFyIGZyb20gdGhpcyBsaW5lIGFsc28gaGF2ZSBhbiB1bnVzdWFsIHJhdGlvIGJldHdlZW4gdGhlIHR3byB2YWx1ZXMgY29tcGFyZWQgdG8gdGhlIG90aGVyIG5vZGVzIGluIHRoZSBzYW1lIGRhdGFzZXQuICAKCiogbmVnYXRpdmUgaHVicyB3aWxsIGJlIGxvY2F0ZWQgY2xvc2UgdG8gdGhlIHVwcGVyIGxlZnQgY29ybmVyOyBwb3NpdGl2ZSBodWJzIHdpbGwgYmUgY2xvc2VyIHRvIHRoZSByaWdodCBvZiB0aGUgcGxvdC4gIAoKRXZlbiB3aXRoIFNQSUVDLUVBU0kgdGhlIG51bWJlciBvZiBwbG90dGVkIG5vZGVzIGlzIGhpZ2ggZm9yIHNvbWUgc3R1ZGllcyAoYnV0IHZlcnkgbG93IGZvciBvdGhlcnMpLCBhbmQgb25lIG1heSBjb25zaWRlciBwbG90dGluZyBhIGdpdmVuIG51bWJlciBvZiBub2RlcyAodGhlIG9uZXMgd2l0aCB0b3AgZGVncmVlPyB0b3AgZWlnZW52ZWN0b3IgY2VudHJhbGl0eT8sIHNvbWUgY29tYmluYXRpb24gb2YgYm90aCkgdG8gc2ltcGxpZnkgdGhlIHBsb3QuICAKU2FpZCB0aGF0LCBhbHRob3VnaCBiZXR3ZWVubmVzcyBjZW50cmFsaXR5IG1heSBiZSBpbnRlcmVzdGluZyB0byBpZGVudGlmeSAiYm90dGxlbmVjayIgdGF4YSwgbG9va2luZyBhdCBiZXR3ZWVubmVzcyBpcyBkaWZmaWN1bHQgKHRoZSBzaGFkZXMgb2YgYWxwaGEgPSB0cmFuc3BhcmVuY3kgYXJlIHZpc3VhbGx5IGRpZmZpY3VsdCB0byBzZXBhcmF0ZSksIGJ1dCBvdGhlcndpc2UgZGVncmVlLCBwb3NpdGl2ZSBkZWdyZWUgYW5kIGFidW5kYW5jZSBhcmUgYWxsIGVhc3kgdG8gdmlzdWFsaXNlLiAgCgojIEVkZ2UgYW5hbHlzaXMuICAKCkVkZ2UgYW5hbHlzaXMgb25seSBtYWtlcyBzZW5zZSBvbmNlIG9uZSBoYXMgc2V0dGxlZCBmb3IgbWVhbmluZ2Z1bCBxdWVzdGlvbnMgYW5kIHNlbGVjdGVkIGEgbGFyZ2UgYW5kIGRpdmVyc2UgZW5vdWdoIHNldCBvZiBzdHVkaWVzLgpRdWVzdGlvbnMgd2hpY2ggbWlnaHQgYmUgcmVsZXZhbnQ6CgoqIGhvdyBzdGFibGUgaXMgYW4gZWRnZT8KCiAgKyB3aXRoaW4gYSBzdHVkeSBhY3Jvc3MgbWV0aG9kcwogIAogICsgYWNyb3NzIHN0dWRpZXMgd2l0aCBhIGdpdmVuIG1ldGhvZAogIAoqIGdpdmVuIGFuIGluZmVyZW5jZSBtZXRob2QsIGNhbiBvbmUgYXNzZW1ibGUgYSBjb25zZW5zdXMgbmV0d29yayB1c2luZyBhcyB3ZWlnaHRzIHRoZSBudW1iZXIgb2YgdGltZXMgYSBnaXZlbiBlZGdlIG9jY3Vycz8gVGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlPwoKKiBob3cgaW1wb3J0YW50IGlzIGFuIGVkZ2UgKGFzIG1lYXN1cmVkIGJ5IGVkZ2UgYmV0d2Vlbm5lc3MpPyBPbmUgbXVzdCBjaG9vc2UgYSBtZWFuaW5nZnVsIGNvbnRleHQgZm9yIHRoaXMKCiogYXJlIGNvLW9jY3VycmVuY2UgcmVsYXRpb25zaGlwcyBtb3JlIGZyZXF1ZW50IGFtb25nIG1lbWJlcnMgb2YgdGhlIHNhbWUgY2xhc3Mgb3IgZmFtaWx5IChhbmQgdGhlIHJldmVyc2UgZm9yIG11dHVhbCBleGNsdXNpb24pPyBUaGlzIG9ubHkgbWFrZXMgc2Vuc2Ugd2l0aGluIGEgZ2l2ZW4gbWV0aG9kIGFuZCBtdXN0IGJlIGNvcnJlY3RlZCBieSB0aGUgZnJlcXVlbmN5IG9mIGVjaCBnaXZlbiBsYXJnZXIgdGF4b24uCgoqIGdpdmVuIGEgKHBvc3NpYmx5IGltcG9ydGFudCkgbWljcm9vcmdhbmlzbSwgd2l0aCB3aGljaCBtaWNyb29yZ2FuaXNtcyBpcyBpdCBtb3N0IGZyZXF1ZW50bHkgYXNzb2NpYXRlZCB3aXRoIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIGFzc29jaWF0aW9ucz8gVGhpcyBvbmx5IG1ha2VzIHNlbnNlIHdpdGhpbiBhIGdpdmVuIG1ldGhvZCBhbmQgb25jZSBvbmUgaGFzIGFuYWx5emVkIGEgbGFyZ2UgbnVtYmVyIG9mIHN0dWRpZXMKCiogZm9yIG1vZGVyYXRlbHkgbGFyZ2UgbmV0d29ya3MsIHdoaWNoIGlzIHRoZSBjdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBvZiBlZGdlIGJldHdlZW5uZXNzPyBEb2VzIHRoaXMgaG9sZCBhbnkgaW1wb3J0YW5jZSBpbiB0ZXJtcyBvZiBzdHJ1Y3R1cmUgb2YgdGhlIG5ldHdvcmtzPwoKYGBge3IgZWRnZV9hbmFseXNpc19iZXR3ZWVuZXNzLCBkcGkgPSA5Nn0KaWYoa2VlcF90aW1lKSB0aWMoIkVkZ2UgYW5hbHlzaXMiKQojIHRoaXMgY2FsY3VsYXRlcyB0aGUgZW1waXJpY2FsIHF1YW50aWxlIG9mIGVkZ2UgYmV0d2Vlbm5lc3MKIyBieSBkYXRhc2V0IGFuZCBtZXRob2Q6IEkgc3VwcG9zZSBvbmUgY2FuIHRoZW4gc2VsZWN0IHRob3NlID4wLjk1CiMgdG8gZ2V0IGEgc29ydCBvZiBodWJuZXNzIGZvciBlZGdlcywgYnV0IGl0IG1ha2VzIHNlbnNlIG9ubHkgZm9yIAojIGxhcmdlIG51bWJlciBvZiBlZGdlcwplZGdlX2xpc3RfZGYgPC0gZWRnZV9saXN0X2RmICU+JSBncm91cF9ieShkYXRhc2V0LCBtZXRob2QpICU+JQogIG11dGF0ZShlYnEgPSBlY2RmKGVkZ2VfYmV0dykoZWRnZV9iZXR3KSkgJT4lIHVuZ3JvdXAoKQoKIyBjYWxjdWxhdGUgdGhlIGZyZXF1ZW5jeSBvZiBlZGdlcywgYnkgc3R1ZHkgYW5kIHR5cGUgYW5kIAojIG1lZGlhbiB2YWx1ZSBhbmQgSVFSIGZvciBJUVIKIyB0aGlzIG9ubHkgY2hlY2tzIGZvciBjb25zZXJ2ZWQgZWRnZXMgYWNyb3NzIG1ldGhvZHMKZWRnZV9mcmVxX2J5c3R1ZHkgPC0gZWRnZV9saXN0X2RmICU+JSAKICBncm91cF9ieShkYXRhc2V0LCBhc3NvX3R5cGUsIGVkZ2VfbmFtZSkgJT4lIAogIHN1bW1hcmlzZShuID0gbigpLAogICAgICAgICAgICBtZWRfZWJxID0gbWVkaWFuKGVicSksCiAgICAgICAgICAgIGlxcl9lYnEgPSBJUVIoZWJxKSkgJT4lCiAgYXJyYW5nZSgtbiwgLW1lZF9lYnEsICkgJT4lIAogIHVuZ3JvdXAoKQoKIyBJIGFtIG5vdyBnZW5lcmF0aW5nIGEgcGxvdCBtYXkgYmUgd2l0aCB0aGUgdG9wIDUwIGVkZ2VzLCBvbmx5IGZvciBGTUJOIHN0dWRpZXMKZWRnZV9mcmVxX2J5c3R1ZHlfRk1CTiA8LSBlZGdlX2ZyZXFfYnlzdHVkeSAlPiUKICBkcGx5cjo6ZmlsdGVyKHN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSkgJT4lCiAgYXJyYW5nZSgtbiwgLW1lZF9lYnEpCgojIGxldCdzIHRyeSBhIHBsb3QgKGdpdmVzIGFuIGVtcGhhc2lzIHRvIG1vc3Qgc3RhYmxlIGVkZ2VzKQpzbGljZV90b19wbG90IDwtIHNsaWNlKGVkZ2VfZnJlcV9ieXN0dWR5X0ZNQk4sIDE6NTApICU+JQogICAgbXV0YXRlKGVkZ2VfbmFtZSA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKGVkZ2VfbmFtZSwgbWVkX2VicSkpIAoKZ2dwbG90KHNsaWNlX3RvX3Bsb3QsIAogICAgICAgbWFwcGluZyA9IGFlcyh4ID0gZWRnZV9uYW1lLCB5ID0gbWVkX2VicSwgc2l6ZSA9IG4sIGNvbG9yID0gYXNzb190eXBlKSkgKwogIGdlb21fcG9pbnQoKSArIAogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gImVkZ2UiLCB5ID0gImVkZ2UgYmV0d2Vlbm5lc3MgcXVhbnRpbGUiLAogICAgICBzaXplID0gImVkZ2UgZnJlcS4iKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoImdyZWVuIiwicmVkIikpCgoKIyBhbm90aGVyIHdheSB0byBzZWUgaXQsIGJhc2VkIG9uIHN0YWJpbGl0eQpzbGljZV90b19wbG90XzIgPC0gc2xpY2UoZWRnZV9mcmVxX2J5c3R1ZHlfRk1CTiwgMTo1MCkgJT4lCiAgICBtdXRhdGUoZWRnZV9uYW1lID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoZWRnZV9uYW1lLCBuKSkKCmdncGxvdChzbGljZV90b19wbG90XzIsIAogICAgICAgbWFwcGluZyA9IGFlcyh4ID0gZWRnZV9uYW1lLCB5ID0gbiwgc2l6ZSA9IG1lZF9lYnEsIGNvbG9yID0gYXNzb190eXBlKSkgKwogIGdlb21fcG9pbnQoKSArIAogIGNvb3JkX2ZsaXAoKSArCiAgbGFicyh4ID0gImVkZ2UiLCB5ID0gImVkZ2UgZnJlcS4iLAogICAgICBzaXplID0gc3RyX3dyYXAoImVkZ2UgYmV0d2Vlbm5lc3MgcXVhbnRpbGUiLCAxNSkpICsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiZ3JlZW4iLCJyZWQiKSkKCmZuIDwtIHBhc3RlKGZpbGUucGF0aChvdXRwdXRfZm9sZGVyLG91dF9maWxlbmFtZV9wcmVmKSwgIl9lZGdlX24udGlmZiIsc2VwPSIiKQpnZ3NhdmUoZmlsZW5hbWUgPSBmbiwgZHBpID0gNjAwLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA3KQpgYGAKCk1hbnkgb2YgdGhlIG1vc3Qgc3RhYmxlIGVkZ2VzIGluY2x1ZGUgZ3V0IGJhY3RlcmlhIGFuZCBwcm9iYWJseSByZXByZXNlbnQgY2x1c3RlciBvZiBpbnRlcmFjdGlvbnMgZnJvbSB0aGlzIGVudmlyb25tZW50IHdoaWNoIGFyZSBjYXJyaWVkIG92ZXIgdG8gbWlsayBhbmQgdGhlbiwgcG9zc2libHksIGNoZWVzZSAoc29tZSBzdHVkaWVzIGFuYWx5emVkIGhlcmUgaW5jbHVkZSBhbHNvIGVudmlyb25tZW50YWwgc2FtcGxlcywgbGlrZSBmZWNlcykuICAKVGhlIGFuYWx5c2lzIGhlcmUgd2FzIGNhcnJpZWQgb3V0IGJ5IGdyb3BpbmcgYnkgZGF0YXNldCBhbmQgYXNzb2NpYXRpb24gdHlwZSwgdGhlcmVmb3JlIHRoZSBmcmVxdWVuY3kgaXMgYnkgZGF0YXNldCBhbmQgYSBmcmVxdWVuY3kgb2YgNCBpbmRpY2F0ZXMgdGhhdCBhIGdpdmVuIGVkZ2Ugd2FzIGRldGVjdGVkIGJ5IGFsbCA0IG1ldGhvZHMgaW4gYSBzaW5nbGUgZGF0YXNldC4gVGhlIGNvZGUgY2FuIGJlIGVhc2lseSBtb2RpZmllZCB0byBkZXRlY3QgZWRnZXMgd2hpY2ggYXJlIGNvbnNlcnZlZCBhY3Jvc3MgZGlmZmVyZW50IHN0dWRpZXMgYW5kIGRpZmZlcmVudCBtZXRob2RzLgpJIHdpbGwgbm93IHRyeSB0byBlc3RpbWF0ZSB0aGUgdGF4b25vbWljIGFzc29ydGF0aXZpdHkuIFRvIGRvIHRoaXMsIHRoZSBvZGRzIHJhdGlvIChPUikgZm9yIGNvcHJlc2VuY2UgbGlua3MgKGdpdmVuIHRoYXQgdGhlIG5vZGVzIGJlbG9uZyB0byB0aGUgc2FtZSBmYW1pbHkpIGFuZCBtdXR1YWwgZXhsdXNpb24gbGlua3MgKGdpdmVuIHRoYXQgdGhlIG5vZGVzIGJlbG9uZyB0byBkaWZmZXJlbnQgZmFtaWxpZXMpIGlzIGNhbGN1bGF0ZWQgYW5kIHRoZSBzaWduaWZpY2FuY2UgaXMgZXZhbHVhdGVkIHVzaW5nIGBlcGkuMmJ5MigpYCBmdW5jdGlvbi4gIAoKCmBgYHtyIGVkZ2VfYW5hbHlzaXNfYXNzb3J0YXRpdml0eSwgZHBpID0gOTZ9CiMgc2FtZSwgYnkgbWV0aG9kIGFuZCBhc3NvdHlwZSwgb25seSBmb3IgRk1CTgojIG9ubHkgbWVhbmluZ2Z1bCBmb3IgaGlnaCBudW1iZXIgb2Ygc3R1ZGllcwplZGdlX2ZyZXFfYnltZXRob2QgPC0gZWRnZV9saXN0X2RmICU+JSAKICBkcGx5cjo6ZmlsdGVyKHN0cl9kZXRlY3QoZGF0YXNldCwgIkZNQk4iKSkgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCBhc3NvX3R5cGUpICU+JSAKICBjb3VudChlZGdlX25hbWUpICU+JQogIGFycmFuZ2UobWV0aG9kLCBhc3NvX3R5cGUsIC1uKQoKIyBUaGUgZm9sbG93aW5nIHNlY3Rpb24gZXZhbHVhdGVzIHRheG9ub21pYyBhc3NvcnRhdGl2aXR5IChpLmUuIGV2YWx1YXRlcyBpZiAKIyBjb3ByZXNlbmNlIGFzc29jaWF0aW9ucyBhcmUgbW9yZSBmcmVxdWVudCBhbW9uZyBtZW1iZXJzIG9mIHRoZSBzYW1lIAojIGZhbWlseSwgb3JkZXIgb3IgY2xhc3MpLiBUaGUgb2RkcyByYXRpbyBvZiBjb3ByZXNlbmNlIHJlbGF0aW9uc2hpcHMgd2l0aGluCiMgdGhlIHNhbWUgZmFtaWx5IGlzIGNhbGN1bGF0ZWQgdXNpbmcgZXBpUjo6ZXBpLjJieTIoKQoKIyBmaXJzdCwgYWRkIHRheG9ub215IHRvIHRoZSAKdW5pcXVlX3RheCA8LSBub2RlX3N0YXRzX2RmICU+JSAKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBkb21haW46c3BlY2llcykgJT4lCiAgZGlzdGluY3QoKQoKZWRnZV9saXN0X2RmX3d0YXhhIDwtIGVkZ2VfbGlzdF9kZgplZGdlX2xpc3RfZGZfd3RheGEgPC0gbGVmdF9qb2luKGVkZ2VfbGlzdF9kZl93dGF4YSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh1bmlxdWVfdGF4LCBsYWJlbCwgY2xhc3MsIG9yZGVyLCBmYW1pbHkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoImZyb21fbmFtZSIgPSAibGFiZWwiKSkgJT4lCiAgZHBseXI6OnJlbmFtZShmcm9tX2NsYXNzID0gY2xhc3MsIGZyb21fb3JkZXIgPSBvcmRlciwgZnJvbV9mYW1pbHkgPSBmYW1pbHkpCmVkZ2VfbGlzdF9kZl93dGF4YSA8LSBsZWZ0X2pvaW4oZWRnZV9saXN0X2RmX3d0YXhhLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHVuaXF1ZV90YXgsIGxhYmVsLCBjbGFzcywgb3JkZXIsIGZhbWlseSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygidG9fbmFtZSIgPSAibGFiZWwiKSkgJT4lCiAgZHBseXI6OnJlbmFtZSh0b19jbGFzcyA9IGNsYXNzLCB0b19vcmRlciA9IG9yZGVyLCB0b19mYW1pbHkgPSBmYW1pbHkpCiMgY2hlY2sgaWYgc2FtZSBmYW1pbHkgb3Igc2FtZSBjbGFzcwplZGdlX2xpc3RfZGZfd3RheGEgPC0gZWRnZV9saXN0X2RmX3d0YXhhICU+JQogIG11dGF0ZShzYW1lX2NsYXNzID0gaWZfZWxzZShmcm9tX2NsYXNzID09IHRvX2NsYXNzLCBULCBGKSwKICAgICAgICAgc2FtZV9vcmRlciA9IGlmX2Vsc2UoZnJvbV9vcmRlciA9PSB0b19vcmRlciwgVCwgRiksCiAgICAgICAgIHNhbWVfZmFtaWx5ID0gaWZfZWxzZShmcm9tX2ZhbWlseSA9PSB0b19mYW1pbHksIFQsIEYpCiAgICAgICAgICkKCiMgdXNlIGEgbG9vcCB0byBjYWxjdWxhdGUgb2RkcyByYXRpb3MgYW5kIHByb2JhYmlsaXRpZXMKIyBnZXQgdGhlIG51bWJlciBvZiBzdHVkaWVzCgplZGdlX2xpc3RfZGZfd3RheGEgPC0gZWRnZV9saXN0X2RmX3d0YXhhICU+JSAKICBzZXBhcmF0ZShkYXRhc2V0LCBpbnRvID0gYygic3R1ZHkiLCAiY29sMiIsICJjb2wzIiksIHJlbW92ZSA9IEYpICU+JQogIHNlbGVjdCgtY29sMiwgLWNvbDMpICU+JQogIG11dGF0ZShzdHVkeSA9IGFzLmZhY3RvcihzdHVkeSksCiAgICAgICAgIHNmID0gZmFjdG9yKHNhbWVfZmFtaWx5LCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpLAogICAgICAgICBzbyA9IGZhY3RvcihzYW1lX29yZGVyLCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpLAogICAgICAgICBzYyA9IGZhY3RvcihzYW1lX2NsYXNzLCBsZXZlbHMgPSBjKCJUUlVFIiwgIkZBTFNFIikpCiAgKQoKYXNzb3J0X3Jlc3VsdHNfbGlzdCA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbmxldmVscyhlZGdlX2xpc3RfZGZfd3RheGEkc3R1ZHkpKQp0YXhvX2xldmVsX3NlbGVjdGlvbiA8LSAiZmFtaWx5Igpmb3IoaSBpbiBzZXFfYWxvbmcobGV2ZWxzKGVkZ2VfbGlzdF9kZl93dGF4YSRzdHVkeSkpKXsKICBpbnB1dF9kZl9zdHVkeSA8LSBlZGdlX2xpc3RfZGZfd3RheGEgJT4lIGRwbHlyOjpmaWx0ZXIoc3R1ZHkgPT0gbGV2ZWxzKHN0dWR5KVtpXSkgJT4lCiAgICBtdXRhdGUobWV0aG9kID0gYXMuZmFjdG9yKG1ldGhvZCkpCiAgaW5uZXJfcmVzdWx0X2xpc3QgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IG5sZXZlbHMoaW5wdXRfZGZfc3R1ZHkkbWV0aG9kKSkKICBmb3IoaiBpbiBzZXFfYWxvbmcobGV2ZWxzKGlucHV0X2RmX3N0dWR5JG1ldGhvZCkpKXsKICAgaW5wdXRfZGZfbWV0aG9kIDwtIGlucHV0X2RmX3N0dWR5ICU+JSAKICAgICAgZHBseXI6OmZpbHRlcihtZXRob2QgPT0gbGV2ZWxzKG1ldGhvZClbal0pCiAgICBpZih2ZXJib3NlX291dHB1dCkgY2F0KCJcblByb2Nlc3NpbmciLCBsZXZlbHMoZWRnZV9saXN0X2RmX3d0YXhhJHN0dWR5KVtpXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJtZXRob2QiLCBsZXZlbHMoaW5wdXRfZGZfbWV0aG9kJG1ldGhvZClbal0sIlxuIikKICAgIGlubmVyX3Jlc3VsdF9saXN0W1tqXV08LXRyeShvZGRzX3JhdGlvKGlucHV0X2RmX21ldGhvZCwgdGF4b19sZXZlbCA9IHRheG9fbGV2ZWxfc2VsZWN0aW9uKSkKICAgIGlmKHZlcmJvc2Vfb3V0cHV0ICYgY2xhc3MoaW5uZXJfcmVzdWx0X2xpc3RbW2pdXSkgPT0gImRhdGEuZnJhbWUiKSB7CiAgICAgIGNhdCgiXG5TdWNjZXNzISBPZGRzIHJhdGlvIGVzdGltYXRlZCIpCiAgICB9IGVsc2UgewogICAgICBjYXQoIlxuRmFpbDogdW5hYmxlIHRvIHJldHVybiB0ZXN0IHJlc3VsdHMhIikKICAgIH0KICAgIG5hbWVzKGlubmVyX3Jlc3VsdF9saXN0KVtqXTwtIGxldmVscyhpbnB1dF9kZl9tZXRob2QkbWV0aG9kKVtqXQogIH0KICBhc3NvcnRfcmVzdWx0c19saXN0W1tpXV0gPC0gaW5uZXJfcmVzdWx0X2xpc3QKICBuYW1lcyhhc3NvcnRfcmVzdWx0c19saXN0KVtpXSA8LSBsZXZlbHMoZWRnZV9saXN0X2RmX3d0YXhhJHN0dWR5KVtpXQp9CiMgY2xlYW4tdXAgdGhlIGxpc3QgYW5kIHB1dCB0b2dldGhlciB0aGUgZGF0YSBmcmFtZXMKCmRmX2xpc3QgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IGxlbmd0aChhc3NvcnRfcmVzdWx0c19saXN0KSkKZm9yKGkgaW4gc2VxX2Fsb25nKGFzc29ydF9yZXN1bHRzX2xpc3QpKXsKICBkZl9saXN0W1tpXV0gPC0gZGZfcmV0dXJuKGFzc29ydF9yZXN1bHRzX2xpc3RbW2ldXSkgCiAgbmFtZXMoZGZfbGlzdClbaV08LW5hbWVzKGFzc29ydF9yZXN1bHRzX2xpc3QpW2ldCn0KCmFzc29ydF9yZXN1bHRzX2RmIDwtIGJpbmRfcm93cyhkZl9saXN0LCAuaWQgPSAic3R1ZHkiKQoKIyBjcmVhdGUgZHVtbXkgT1Jlc3QgdmFsdWVzIGJ5IHJlcGxhY2luZyBJbmYKYXNzb3J0X3Jlc3VsdHNfZGYgPC0gYXNzb3J0X3Jlc3VsdHNfZGYgJT4lCiAgbXV0YXRlKE9SX2VzdF93ZHVtbXkgPSBpZl9lbHNlKE9SX2VzdD09SW5mLCA5OSwgT1JfZXN0KSkgJT4lCiAgbXV0YXRlKGxPUl9lc3Rfd2R1bW15ID0gbG9nKE9SX2VzdF93ZHVtbXkpLAogICAgICAgICBzaWduaWZpY2FudCA9IGlmX2Vsc2UoT1JfcC52YWx1ZS4xcyA8PTAuMDUsIFQsIEYpKSAKCiMgcGxvdCwgc3R1ZGllcyBhcmUgcG9vbGVkCmdncGxvdChhc3NvcnRfcmVzdWx0c19kZiwgbWFwcGluZyA9IGFlcyh4ID0gYXNzb3J0X3Rlc3QsIHkgPSBsT1JfZXN0X3dkdW1teSwgY29sb3VyID0gc2lnbmlmaWNhbnQpKSArCiAgZmFjZXRfd3JhcCh+bWV0aG9kKSArCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjIpICsKICBsYWJzKHkgPSAibG9nKG9kZHMgcmF0aW8pIikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIHRhYnVsYXRpbmcKeHRhYnMofiBtZXRob2QgKyBzaWduaWZpY2FudCArYXNzb3J0X3Rlc3QgLCBkYXRhID0gYXNzb3J0X3Jlc3VsdHNfZGYpCgpybShhc3NvcnRfcmVzdWx0c19saXN0LCBpbnB1dF9kZl9zdHVkeSwgaW5uZXJfcmVzdWx0X2xpc3QsIGlucHV0X2RmX21ldGhvZCwgaSwgaiwgZGZfbGlzdCkKCmlmKHBsYXlfYXVkaW8pIGJlZXAoc291bmQgPSA2KQppZihrZWVwX3RpbWUpIHRvYygpCgpgYGAKRXZlbiBpZiB0aGUgb2RkcyByYXRpb3MgYXJlIHZlcnkgaGlnaCBpbiBzb21lIGNhc2VzIChhY3R1YWxseSBJbmYsIGR1ZSB0byBkaXZpc2lvbiBieSAwKSB0aGV5IGFyZSBub3Qgc2lnbmlmaWNhbnQuIE15IGNvbmNsdXNpb24gaXMgdGhhdCB0aGVyZSBpcyBub3Qgc3VmZmljaWVudCBzdXBwb3J0IGZvciB0aGUgb2NjdXJyZW5jZSBvZiB0YXhvbm9taWMgYXNzb3J0YXRpdml0eSAoaS5lLiBmb3IgYSBzaWduaWZpY2FudGx5IGhpZ2hlciBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgYSBjb3ByZXNlbmNlIGxpbmsgYmV0d2VlbiBtZW1iZXJzIG9mIHRoZSBzYW1lIGZhbWlseSkuICAKU2ltaWxhciByZXN1bHRzIGNhbiBiZSBvYnRhaW5lZCB3aGVuIGxvb2tpbmcgYXQgdGhlIG9yZGVyIGFuZCBjbGFzcyBsZXZlbC4gIAoKCiMgQ2l0YXRpb25zIGFuZCBjb3B5cmlnaHQuICAKCiMjIENpdGF0aW9ucy4gIAoKQ2l0YXRpb25zIGZvciB0aGUgbWV0YXRheG9ub21pYyBzdHVkaWVzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhcmUgaW4gdGhlIHN0dWR5X21ldGFkYXRhIGRhdGEgZnJhbWUgKGlmIGFueSkuIFRoZSBtYWluIHBhY2thZ2UgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGlzIE5ldENvTWksIGFuZCBvZiBjb3Vyc2UsIFBoeWxvc2VxLiBDaXRhdGlvbnMgZm9yIGFsbCBwYWNrYWdlcyBhcmUgYmVsb3cuCgpgYGB7ciBjaXRhdGlvbnN9CgphbGxfcGFja2FnZXMgPC0gYygiYmFzZSIsIC5jcmFuX3BhY2thZ2VzLCAuYmlvY19wYWNrYWdlcywgLmdpdGh1Yl9wYWNrYWdlcykKbWFwKGFsbF9wYWNrYWdlcywgY2l0YXRpb24pCgojIGZvciByZXByb2R1Y2liaWxpdHkgeW91IHNob3VsZCBhbHNvIHJ1bgojIHNlc3Npb25JbmZvKCkKCgoKYGBgCgojIyBDb3B5cmlnaHQgbm90aWNlLiAgCgpTY3JpcHQgY3JlYXRlZCBieSBFdWdlbmlvIFBhcmVudGUgKGV1Z2VuaW8ucGFyZW50ZUB1bmliYXMuaXQpLCBVbml2ZXJzaXTDoCBkZWdsaSBTdHVkaSBkZWxsYSBCYXNpbGljYXRhLCAyMDIxCmh0dHBzOi8vZ2l0aHViLmNvbS9lcDE0MgpUaGlzIHNjcmlwdCBoYXMgYmVlbiB0ZXN0ZWQgd2l0aCBSIDQuMC41IGFuZCA0LjEgb24gdHdvIGRpZmZlcmVudCBBcHBsZSBjb21wdXRlcnMgd2l0aCA4IEdCIFJBTSBydW5uaW5nIE1hY09TIDEwLjE0LjYuICAKUGVybWlzc2lvbiBpcyBoZXJlYnkgZ3JhbnRlZCwgZnJlZSBvZiBjaGFyZ2UsIHRvIGFueSBwZXJzb24gb2J0YWluaW5nIGEgY29weSBvZiB0aGlzIHNvZnR3YXJlIGFuZCBhc3NvY2lhdGVkIGRvY3VtZW50YXRpb24gZmlsZXMgKHRoZSBcIlNvZnR3YXJlXCIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczogIApUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUiBJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIEFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIgTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwgT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUgU09GVFdBUkUuCg==